Compare commits
9 Commits
android-25
...
android-25
Author | SHA1 | Date | |
---|---|---|---|
f58de25587 | |||
434a5c8685 | |||
28919e2687 | |||
8dea5adc59 | |||
c52637925f | |||
87585c765d | |||
638ac4b834 | |||
b80685993c | |||
4ebf892607 |
@ -5,10 +5,9 @@
|
||||
| [12749](https://github.com/yuzu-emu/yuzu//pull/12749) | [`aad4b0d6f`](https://github.com/yuzu-emu/yuzu//pull/12749/files) | general: workarounds for SMMU syncing issues | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [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 |
|
||||
| [13017](https://github.com/yuzu-emu/yuzu//pull/13017) | [`af4248256`](https://github.com/yuzu-emu/yuzu//pull/13017/files) | kernel: add and enable system suspend type | [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 |
|
||||
| [13034](https://github.com/yuzu-emu/yuzu//pull/13034) | [`c4500677a`](https://github.com/yuzu-emu/yuzu//pull/13034/files) | android: Input mapping | [t895](https://github.com/t895/) | 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) {
|
||||
@ -173,7 +167,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
super.onResume()
|
||||
nfcReader.startScanning()
|
||||
startMotionSensorListener()
|
||||
InputHandler.updateControllerData()
|
||||
|
||||
buildPictureInPictureParams()
|
||||
}
|
||||
@ -198,7 +191,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
nfcReader.onNewIntent(intent)
|
||||
InputHandler.updateControllerData()
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
|
@ -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(
|
||||
|
@ -57,7 +57,7 @@ object NativeInput {
|
||||
/**
|
||||
* Handles axis movement events.
|
||||
* @param guid 32 character hexadecimal string consisting of the controller's PID+VID.
|
||||
* @param port Port determined by controller connection order.
|
||||
* @param port Port determined by controller connection order
|
||||
* @param axis The axis ID.
|
||||
* @param value Value along the given axis.
|
||||
*/
|
||||
@ -265,6 +265,11 @@ object NativeInput {
|
||||
*/
|
||||
external fun stopMapping()
|
||||
|
||||
/**
|
||||
* Removes the loaded input profiles from memory.
|
||||
*/
|
||||
external fun unloadInputProfiles()
|
||||
|
||||
/**
|
||||
* Updates a controller's mappings with auto-mapping params.
|
||||
* @param playerIndex Index of the player to auto-map.
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,6 +137,7 @@ class SettingsActivity : AppCompatActivity() {
|
||||
super.onStop()
|
||||
Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
|
||||
if (isFinishing) {
|
||||
NativeInput.unloadInputProfiles()
|
||||
NativeInput.reloadInputDevices()
|
||||
NativeLibrary.applySettings()
|
||||
if (args.game == null) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
@ -383,67 +382,22 @@ class SettingsFragmentPresenter(
|
||||
)
|
||||
)
|
||||
|
||||
InputHandler.updateControllerData()
|
||||
|
||||
val autoMappingSetting = object : AbstractIntSetting {
|
||||
override val key = "auto_mapping_device"
|
||||
|
||||
override fun getInt(needsGlobal: Boolean): Int = -1
|
||||
|
||||
override fun setInt(value: Int) {
|
||||
val registeredController = InputHandler.registeredControllers[value + 1]
|
||||
val displayName = registeredController.get(
|
||||
"display",
|
||||
context.getString(R.string.unknown)
|
||||
)
|
||||
NativeInput.updateMappingsWithDefault(
|
||||
playerIndex,
|
||||
registeredController,
|
||||
displayName
|
||||
)
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.attempted_auto_map, displayName),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
|
||||
override val defaultValue = -1
|
||||
|
||||
override fun getValueAsString(needsGlobal: Boolean) = getInt().toString()
|
||||
|
||||
override fun reset() = setInt(defaultValue)
|
||||
|
||||
override val isRuntimeModifiable: Boolean = true
|
||||
}
|
||||
|
||||
val unknownString = context.getString(R.string.unknown)
|
||||
val prettyAutoMappingControllerList = InputHandler.registeredControllers.mapNotNull {
|
||||
val port = it.get("port", -1)
|
||||
return@mapNotNull if (port == 100 || port == -1) {
|
||||
null
|
||||
} else {
|
||||
it.get("display", unknownString)
|
||||
}
|
||||
}.toTypedArray()
|
||||
add(
|
||||
IntSingleChoiceSetting(
|
||||
autoMappingSetting,
|
||||
titleId = R.string.auto_map,
|
||||
descriptionId = R.string.auto_map_description,
|
||||
choices = prettyAutoMappingControllerList,
|
||||
values = IntArray(prettyAutoMappingControllerList.size) { it }.toTypedArray()
|
||||
)
|
||||
)
|
||||
|
||||
val mappingFilterSetting = object : AbstractIntSetting {
|
||||
override val key = "mapping_filter"
|
||||
val devicesSetting = object : AbstractIntSetting {
|
||||
override val key = "input_device"
|
||||
|
||||
override fun getInt(needsGlobal: Boolean): Int = settingsViewModel.currentDevice
|
||||
|
||||
override fun setInt(value: Int) {
|
||||
if (value != 0) {
|
||||
val registeredController = InputHandler.registeredControllers[value]
|
||||
NativeInput.updateMappingsWithDefault(
|
||||
playerIndex,
|
||||
registeredController,
|
||||
registeredController.get("display", context.getString(R.string.unknown))
|
||||
)
|
||||
}
|
||||
settingsViewModel.currentDevice = value
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
|
||||
override val defaultValue = 0
|
||||
@ -455,6 +409,8 @@ class SettingsFragmentPresenter(
|
||||
override val isRuntimeModifiable: Boolean = true
|
||||
}
|
||||
|
||||
InputHandler.updateControllerData()
|
||||
val unknownString = context.getString(R.string.unknown)
|
||||
val prettyControllerList = InputHandler.registeredControllers.mapNotNull {
|
||||
return@mapNotNull if (it.get("port", 0) == 100) {
|
||||
null
|
||||
@ -464,9 +420,9 @@ class SettingsFragmentPresenter(
|
||||
}.toTypedArray()
|
||||
add(
|
||||
IntSingleChoiceSetting(
|
||||
mappingFilterSetting,
|
||||
titleId = R.string.input_mapping_filter,
|
||||
descriptionId = R.string.input_mapping_filter_description,
|
||||
devicesSetting,
|
||||
titleId = R.string.input_device,
|
||||
descriptionId = R.string.input_device_description,
|
||||
choices = prettyControllerList,
|
||||
values = IntArray(prettyControllerList.size) { it }.toTypedArray()
|
||||
)
|
||||
|
@ -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
|
||||
@ -44,13 +47,14 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
||||
binding.textSettingValue.text = item.getChoiceAt(item.getSelectedValue())
|
||||
}
|
||||
}
|
||||
if (binding.textSettingValue.text.isEmpty()) {
|
||||
binding.textSettingValue.setVisible(false)
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -21,12 +21,8 @@ object InputHandler {
|
||||
else -> return false
|
||||
}
|
||||
|
||||
var controllerData = androidControllers[event.device.controllerNumber]
|
||||
if (controllerData == null) {
|
||||
updateControllerData()
|
||||
controllerData = androidControllers[event.device.controllerNumber] ?: return false
|
||||
}
|
||||
|
||||
val controllerData =
|
||||
androidControllers[event.device.controllerNumber] ?: return false
|
||||
NativeInput.onGamePadButtonEvent(
|
||||
controllerData.getGUID(),
|
||||
controllerData.getPort(),
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,7 +262,6 @@ jobjectArray Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getInputDevices(
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_loadInputProfiles(JNIEnv* env,
|
||||
jobject j_obj) {
|
||||
map_profiles.clear();
|
||||
const auto input_profile_loc =
|
||||
Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "input";
|
||||
|
||||
@ -433,6 +434,11 @@ void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_stopMapping(JNIEnv* env,
|
||||
EmulationSession::GetInstance().GetInputSubsystem().StopMapping();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_unloadInputProfiles(JNIEnv* env,
|
||||
jobject j_obj) {
|
||||
map_profiles.clear();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_updateMappingsWithDefaultImpl(
|
||||
JNIEnv* env, jobject j_obj, jint j_player_index, jstring j_device_params,
|
||||
jstring j_display_name) {
|
||||
|
@ -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" />
|
||||
|
||||
|
@ -300,11 +300,8 @@
|
||||
<string name="unused">Unused</string>
|
||||
<string name="input_prompt">Move or press an input</string>
|
||||
<string name="unsupported_input">Unsupported input type</string>
|
||||
<string name="input_mapping_filter">Input mapping filter</string>
|
||||
<string name="input_mapping_filter_description">Select a device to filter mapping inputs</string>
|
||||
<string name="auto_map">Auto-map a controller</string>
|
||||
<string name="auto_map_description">Select a device to attempt auto-mapping</string>
|
||||
<string name="attempted_auto_map">Attempted auto-map with %1$s</string>
|
||||
<string name="input_device">Input device</string>
|
||||
<string name="input_device_description">Select an input device for auto-mapping. This will also act as a filter while mapping inputs.\nThis is temporary and not meant to lock this player to a specific controller.</string>
|
||||
<string name="controller_type">Controller type</string>
|
||||
<string name="pro_controller">Pro Controller</string>
|
||||
<string name="handheld">Handheld</string>
|
||||
|
@ -396,9 +396,9 @@ struct PlayerInput {
|
||||
u32 button_color_right;
|
||||
std::string profile_name;
|
||||
|
||||
// This is meant to tell the Android frontend whether to use a device's built-in vibration
|
||||
// motor or a controller's vibrations.
|
||||
#ifdef ANDROID
|
||||
bool use_system_vibrator;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct TouchscreenInput {
|
||||
|
@ -423,12 +423,12 @@ add_library(core STATIC
|
||||
hle/service/am/applet_manager.h
|
||||
hle/service/am/applet_message_queue.cpp
|
||||
hle/service/am/applet_message_queue.h
|
||||
hle/service/am/display_layer_manager.cpp
|
||||
hle/service/am/display_layer_manager.h
|
||||
hle/service/am/hid_registration.cpp
|
||||
hle/service/am/hid_registration.h
|
||||
hle/service/am/library_applet_storage.cpp
|
||||
hle/service/am/library_applet_storage.h
|
||||
hle/service/am/managed_layer_holder.cpp
|
||||
hle/service/am/managed_layer_holder.h
|
||||
hle/service/am/process.cpp
|
||||
hle/service/am/process.h
|
||||
hle/service/am/service/all_system_applet_proxies_service.cpp
|
||||
@ -481,6 +481,8 @@ add_library(core STATIC
|
||||
hle/service/am/service/system_applet_proxy.h
|
||||
hle/service/am/service/window_controller.cpp
|
||||
hle/service/am/service/window_controller.h
|
||||
hle/service/am/system_buffer_manager.cpp
|
||||
hle/service/am/system_buffer_manager.h
|
||||
hle/service/aoc/aoc_u.cpp
|
||||
hle/service/aoc/aoc_u.h
|
||||
hle/service/apm/apm.cpp
|
||||
@ -489,12 +491,12 @@ add_library(core STATIC
|
||||
hle/service/apm/apm_controller.h
|
||||
hle/service/apm/apm_interface.cpp
|
||||
hle/service/apm/apm_interface.h
|
||||
hle/service/audio/audctl.cpp
|
||||
hle/service/audio/audctl.h
|
||||
hle/service/audio/audin_u.cpp
|
||||
hle/service/audio/audin_u.h
|
||||
hle/service/audio/audio.cpp
|
||||
hle/service/audio/audio.h
|
||||
hle/service/audio/audio_controller.cpp
|
||||
hle/service/audio/audio_controller.h
|
||||
hle/service/audio/audout_u.cpp
|
||||
hle/service/audio/audout_u.h
|
||||
hle/service/audio/audrec_a.cpp
|
||||
@ -543,16 +545,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
|
||||
@ -747,48 +739,15 @@ add_library(core STATIC
|
||||
hle/service/nim/nim.h
|
||||
hle/service/npns/npns.cpp
|
||||
hle/service/npns/npns.h
|
||||
hle/service/ns/account_proxy_interface.cpp
|
||||
hle/service/ns/account_proxy_interface.h
|
||||
hle/service/ns/application_manager_interface.cpp
|
||||
hle/service/ns/application_manager_interface.h
|
||||
hle/service/ns/application_version_interface.cpp
|
||||
hle/service/ns/application_version_interface.h
|
||||
hle/service/ns/content_management_interface.cpp
|
||||
hle/service/ns/content_management_interface.h
|
||||
hle/service/ns/develop_interface.cpp
|
||||
hle/service/ns/develop_interface.h
|
||||
hle/service/ns/document_interface.cpp
|
||||
hle/service/ns/document_interface.h
|
||||
hle/service/ns/download_task_interface.cpp
|
||||
hle/service/ns/download_task_interface.h
|
||||
hle/service/ns/dynamic_rights_interface.cpp
|
||||
hle/service/ns/dynamic_rights_interface.h
|
||||
hle/service/ns/ecommerce_interface.cpp
|
||||
hle/service/ns/ecommerce_interface.h
|
||||
hle/service/ns/factory_reset_interface.cpp
|
||||
hle/service/ns/factory_reset_interface.h
|
||||
hle/service/ns/errors.h
|
||||
hle/service/ns/iplatform_service_manager.cpp
|
||||
hle/service/ns/iplatform_service_manager.h
|
||||
hle/service/ns/language.cpp
|
||||
hle/service/ns/language.h
|
||||
hle/service/ns/ns_results.h
|
||||
hle/service/ns/ns_types.h
|
||||
hle/service/ns/ns.cpp
|
||||
hle/service/ns/ns.h
|
||||
hle/service/ns/platform_service_manager.cpp
|
||||
hle/service/ns/platform_service_manager.h
|
||||
hle/service/ns/query_service.cpp
|
||||
hle/service/ns/query_service.h
|
||||
hle/service/ns/read_only_application_control_data_interface.cpp
|
||||
hle/service/ns/read_only_application_control_data_interface.h
|
||||
hle/service/ns/read_only_application_record_interface.cpp
|
||||
hle/service/ns/read_only_application_record_interface.h
|
||||
hle/service/ns/service_getter_interface.cpp
|
||||
hle/service/ns/service_getter_interface.h
|
||||
hle/service/ns/system_update_control.cpp
|
||||
hle/service/ns/system_update_control.h
|
||||
hle/service/ns/system_update_interface.cpp
|
||||
hle/service/ns/system_update_interface.h
|
||||
hle/service/ns/vulnerability_manager_interface.cpp
|
||||
hle/service/ns/vulnerability_manager_interface.h
|
||||
hle/service/ns/pdm_qry.cpp
|
||||
hle/service/ns/pdm_qry.h
|
||||
hle/service/nvdrv/core/container.cpp
|
||||
hle/service/nvdrv/core/container.h
|
||||
hle/service/nvdrv/core/heap_mapper.cpp
|
||||
@ -841,12 +800,12 @@ add_library(core STATIC
|
||||
hle/service/nvnflinger/consumer_base.cpp
|
||||
hle/service/nvnflinger/consumer_base.h
|
||||
hle/service/nvnflinger/consumer_listener.h
|
||||
hle/service/nvnflinger/fb_share_buffer_manager.cpp
|
||||
hle/service/nvnflinger/fb_share_buffer_manager.h
|
||||
hle/service/nvnflinger/graphic_buffer_producer.cpp
|
||||
hle/service/nvnflinger/graphic_buffer_producer.h
|
||||
hle/service/nvnflinger/hos_binder_driver_server.cpp
|
||||
hle/service/nvnflinger/hos_binder_driver_server.h
|
||||
hle/service/nvnflinger/hos_binder_driver.cpp
|
||||
hle/service/nvnflinger/hos_binder_driver.h
|
||||
hle/service/nvnflinger/hardware_composer.cpp
|
||||
hle/service/nvnflinger/hardware_composer.h
|
||||
hle/service/nvnflinger/hwc_layer.h
|
||||
@ -856,8 +815,6 @@ add_library(core STATIC
|
||||
hle/service/nvnflinger/pixel_format.h
|
||||
hle/service/nvnflinger/producer_listener.h
|
||||
hle/service/nvnflinger/status.h
|
||||
hle/service/nvnflinger/surface_flinger.cpp
|
||||
hle/service/nvnflinger/surface_flinger.h
|
||||
hle/service/nvnflinger/ui/fence.h
|
||||
hle/service/nvnflinger/ui/graphic_buffer.cpp
|
||||
hle/service/nvnflinger/ui/graphic_buffer.h
|
||||
@ -951,8 +908,6 @@ add_library(core STATIC
|
||||
hle/service/server_manager.h
|
||||
hle/service/service.cpp
|
||||
hle/service/service.h
|
||||
hle/service/services.cpp
|
||||
hle/service/services.h
|
||||
hle/service/set/setting_formats/appln_settings.cpp
|
||||
hle/service/set/setting_formats/appln_settings.h
|
||||
hle/service/set/setting_formats/device_settings.cpp
|
||||
@ -1000,26 +955,22 @@ add_library(core STATIC
|
||||
hle/service/ssl/ssl_backend.h
|
||||
hle/service/usb/usb.cpp
|
||||
hle/service/usb/usb.h
|
||||
hle/service/vi/display/vi_display.cpp
|
||||
hle/service/vi/display/vi_display.h
|
||||
hle/service/vi/layer/vi_layer.cpp
|
||||
hle/service/vi/layer/vi_layer.h
|
||||
hle/service/vi/application_display_service.cpp
|
||||
hle/service/vi/application_display_service.h
|
||||
hle/service/vi/application_root_service.cpp
|
||||
hle/service/vi/application_root_service.h
|
||||
hle/service/vi/conductor.cpp
|
||||
hle/service/vi/conductor.h
|
||||
hle/service/vi/container.cpp
|
||||
hle/service/vi/container.h
|
||||
hle/service/vi/display_list.h
|
||||
hle/service/vi/display.h
|
||||
hle/service/vi/layer_list.h
|
||||
hle/service/vi/layer.h
|
||||
hle/service/vi/hos_binder_driver.cpp
|
||||
hle/service/vi/hos_binder_driver.h
|
||||
hle/service/vi/manager_display_service.cpp
|
||||
hle/service/vi/manager_display_service.h
|
||||
hle/service/vi/manager_root_service.cpp
|
||||
hle/service/vi/manager_root_service.h
|
||||
hle/service/vi/service_creator.cpp
|
||||
hle/service/vi/service_creator.h
|
||||
hle/service/vi/shared_buffer_manager.cpp
|
||||
hle/service/vi/shared_buffer_manager.h
|
||||
hle/service/vi/system_display_service.cpp
|
||||
hle/service/vi/system_display_service.h
|
||||
hle/service/vi/system_root_service.cpp
|
||||
@ -1028,8 +979,6 @@ add_library(core STATIC
|
||||
hle/service/vi/vi_types.h
|
||||
hle/service/vi/vi.cpp
|
||||
hle/service/vi/vi.h
|
||||
hle/service/vi/vsync_manager.cpp
|
||||
hle/service/vi/vsync_manager.h
|
||||
internal_network/network.cpp
|
||||
internal_network/network.h
|
||||
internal_network/network_interface.cpp
|
||||
|
@ -47,7 +47,6 @@
|
||||
#include "core/hle/service/psc/time/system_clock.h"
|
||||
#include "core/hle/service/psc/time/time_zone_service.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/services.h"
|
||||
#include "core/hle/service/set/system_settings_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/internal_network/network.h"
|
||||
@ -311,8 +310,7 @@ struct System::Impl {
|
||||
audio_core = std::make_unique<AudioCore::AudioCore>(system);
|
||||
|
||||
service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
|
||||
services =
|
||||
std::make_unique<Service::Services>(service_manager, system, stop_event.get_token());
|
||||
services = std::make_unique<Service::Services>(service_manager, system);
|
||||
|
||||
is_powered_on = true;
|
||||
exit_locked = false;
|
||||
@ -460,10 +458,11 @@ struct System::Impl {
|
||||
gpu_core->NotifyShutdown();
|
||||
}
|
||||
|
||||
stop_event.request_stop();
|
||||
core_timing.SyncPause(false);
|
||||
Network::CancelPendingSocketOperations();
|
||||
kernel.SuspendEmulation(true);
|
||||
if (services) {
|
||||
services->KillNVNFlinger();
|
||||
}
|
||||
kernel.CloseServices();
|
||||
kernel.ShutdownCores();
|
||||
applet_manager.Reset();
|
||||
@ -481,7 +480,6 @@ struct System::Impl {
|
||||
cpu_manager.Shutdown();
|
||||
debugger.reset();
|
||||
kernel.Shutdown();
|
||||
stop_event = {};
|
||||
Network::RestartSocketOperations();
|
||||
|
||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
||||
@ -617,7 +615,6 @@ struct System::Impl {
|
||||
|
||||
ExecuteProgramCallback execute_program_callback;
|
||||
ExitCallback exit_callback;
|
||||
std::stop_source stop_event;
|
||||
|
||||
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
|
||||
std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_cpu{};
|
||||
|
@ -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.");
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include "core/file_sys/system_archive/data/font_standard.h"
|
||||
#include "core/file_sys/system_archive/shared_font.h"
|
||||
#include "core/file_sys/vfs/vfs_vector.h"
|
||||
#include "core/hle/service/ns/platform_service_manager.h"
|
||||
#include "core/hle/service/ns/iplatform_service_manager.h"
|
||||
|
||||
namespace FileSys::SystemArchive {
|
||||
|
||||
|
@ -329,8 +329,9 @@ bool ProfileManager::GetProfileBaseAndData(const ProfileInfo& user, ProfileBase&
|
||||
|
||||
/// Returns if the system is allowing user registrations or not
|
||||
bool ProfileManager::CanSystemRegisterUser() const {
|
||||
// TODO: Both games and applets can register users. Determine when this condition is not meet.
|
||||
return true;
|
||||
return false; // TODO(ogniK): Games shouldn't have
|
||||
// access to user registration, when we
|
||||
// emulate qlaunch. Update this to dynamically change.
|
||||
}
|
||||
|
||||
bool ProfileManager::RemoveUser(UUID uuid) {
|
||||
|
@ -8,13 +8,13 @@
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
void LoopProcess(Core::System& system) {
|
||||
void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
server_manager->RegisterNamedService("appletAE",
|
||||
std::make_shared<IAllSystemAppletProxiesService>(system));
|
||||
server_manager->RegisterNamedService("appletOE",
|
||||
std::make_shared<IApplicationProxyService>(system));
|
||||
server_manager->RegisterNamedService(
|
||||
"appletAE", std::make_shared<IAllSystemAppletProxiesService>(system, nvnflinger));
|
||||
server_manager->RegisterNamedService(
|
||||
"appletOE", std::make_shared<IApplicationProxyService>(system, nvnflinger));
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,12 @@ namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::Nvnflinger {
|
||||
class Nvnflinger;
|
||||
}
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
void LoopProcess(Core::System& system);
|
||||
void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system);
|
||||
|
||||
} // namespace Service::AM
|
||||
|
@ -14,9 +14,10 @@
|
||||
|
||||
#include "core/hle/service/am/am_types.h"
|
||||
#include "core/hle/service/am/applet_message_queue.h"
|
||||
#include "core/hle/service/am/display_layer_manager.h"
|
||||
#include "core/hle/service/am/hid_registration.h"
|
||||
#include "core/hle/service/am/managed_layer_holder.h"
|
||||
#include "core/hle/service/am/process.h"
|
||||
#include "core/hle/service/am/system_buffer_manager.h"
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
@ -53,7 +54,8 @@ struct Applet {
|
||||
HidRegistration hid_registration;
|
||||
|
||||
// vi state
|
||||
DisplayLayerManager display_layer_manager{};
|
||||
SystemBufferManager system_buffer_manager{};
|
||||
ManagedLayerHolder managed_layer_holder{};
|
||||
|
||||
// Applet common functions
|
||||
Result terminate_result{};
|
||||
|
@ -35,21 +35,6 @@ AppletStorageChannel& InitializeFakeCallerApplet(Core::System& system,
|
||||
return applet->caller_applet_broker->GetInData();
|
||||
}
|
||||
|
||||
void PushInShowQlaunch(Core::System& system, AppletStorageChannel& channel) {
|
||||
const CommonArguments arguments{
|
||||
.arguments_version = CommonArgumentVersion::Version3,
|
||||
.size = CommonArgumentSize::Version3,
|
||||
.library_version = 0,
|
||||
.theme_color = ThemeColor::BasicBlack,
|
||||
.play_startup_sound = true,
|
||||
.system_tick = system.CoreTiming().GetClockTicks(),
|
||||
};
|
||||
|
||||
std::vector<u8> argument_data(sizeof(arguments));
|
||||
std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
|
||||
channel.Push(std::make_shared<IStorage>(system, std::move(argument_data)));
|
||||
}
|
||||
|
||||
void PushInShowAlbum(Core::System& system, AppletStorageChannel& channel) {
|
||||
const CommonArguments arguments{
|
||||
.arguments_version = CommonArgumentVersion::Version3,
|
||||
@ -299,9 +284,6 @@ void AppletManager::CreateAndInsertByFrontendAppletParameters(
|
||||
|
||||
// Starting from frontend, some applets require input data.
|
||||
switch (applet->applet_id) {
|
||||
case AppletId::QLaunch:
|
||||
PushInShowQlaunch(m_system, InitializeFakeCallerApplet(m_system, applet));
|
||||
break;
|
||||
case AppletId::Cabinet:
|
||||
PushInShowCabinetData(m_system, InitializeFakeCallerApplet(m_system, applet));
|
||||
break;
|
||||
|
@ -1,151 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/am/display_layer_manager.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/hle/service/vi/application_display_service.h"
|
||||
#include "core/hle/service/vi/container.h"
|
||||
#include "core/hle/service/vi/manager_display_service.h"
|
||||
#include "core/hle/service/vi/manager_root_service.h"
|
||||
#include "core/hle/service/vi/shared_buffer_manager.h"
|
||||
#include "core/hle/service/vi/vi_results.h"
|
||||
#include "core/hle/service/vi/vi_types.h"
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
DisplayLayerManager::DisplayLayerManager() = default;
|
||||
DisplayLayerManager::~DisplayLayerManager() {
|
||||
this->Finalize();
|
||||
}
|
||||
|
||||
void DisplayLayerManager::Initialize(Core::System& system, Kernel::KProcess* process,
|
||||
AppletId applet_id, LibraryAppletMode mode) {
|
||||
R_ASSERT(system.ServiceManager()
|
||||
.GetService<VI::IManagerRootService>("vi:m", true)
|
||||
->GetDisplayService(&m_display_service, VI::Policy::Compositor));
|
||||
R_ASSERT(m_display_service->GetManagerDisplayService(&m_manager_display_service));
|
||||
|
||||
m_process = process;
|
||||
m_system_shared_buffer_id = 0;
|
||||
m_system_shared_layer_id = 0;
|
||||
m_applet_id = applet_id;
|
||||
m_buffer_sharing_enabled = false;
|
||||
m_blending_enabled = mode == LibraryAppletMode::PartialForeground ||
|
||||
mode == LibraryAppletMode::PartialForegroundIndirectDisplay;
|
||||
}
|
||||
|
||||
void DisplayLayerManager::Finalize() {
|
||||
if (!m_manager_display_service) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up managed layers.
|
||||
for (const auto& layer : m_managed_display_layers) {
|
||||
m_manager_display_service->DestroyManagedLayer(layer);
|
||||
}
|
||||
|
||||
for (const auto& layer : m_managed_display_recording_layers) {
|
||||
m_manager_display_service->DestroyManagedLayer(layer);
|
||||
}
|
||||
|
||||
// Clean up shared layers.
|
||||
if (m_buffer_sharing_enabled) {
|
||||
m_manager_display_service->DestroySharedLayerSession(m_process);
|
||||
}
|
||||
|
||||
m_manager_display_service = nullptr;
|
||||
m_display_service = nullptr;
|
||||
}
|
||||
|
||||
Result DisplayLayerManager::CreateManagedDisplayLayer(u64* out_layer_id) {
|
||||
R_UNLESS(m_manager_display_service != nullptr, VI::ResultOperationFailed);
|
||||
|
||||
// TODO(Subv): Find out how AM determines the display to use, for now just
|
||||
// create the layer in the Default display.
|
||||
u64 display_id;
|
||||
R_TRY(m_display_service->OpenDisplay(&display_id, VI::DisplayName{"Default"}));
|
||||
R_TRY(m_manager_display_service->CreateManagedLayer(
|
||||
out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()}));
|
||||
|
||||
m_manager_display_service->SetLayerVisibility(m_visible, *out_layer_id);
|
||||
m_managed_display_layers.emplace(*out_layer_id);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DisplayLayerManager::CreateManagedDisplaySeparableLayer(u64* out_layer_id,
|
||||
u64* out_recording_layer_id) {
|
||||
R_UNLESS(m_manager_display_service != nullptr, VI::ResultOperationFailed);
|
||||
|
||||
// TODO(Subv): Find out how AM determines the display to use, for now just
|
||||
// create the layer in the Default display.
|
||||
// This calls nn::vi::CreateRecordingLayer() which creates another layer.
|
||||
// Currently we do not support more than 1 layer per display, output 1 layer id for now.
|
||||
// Outputting 1 layer id instead of the expected 2 has not been observed to cause any adverse
|
||||
// side effects.
|
||||
*out_recording_layer_id = 0;
|
||||
R_RETURN(this->CreateManagedDisplayLayer(out_layer_id));
|
||||
}
|
||||
|
||||
Result DisplayLayerManager::IsSystemBufferSharingEnabled() {
|
||||
// Succeed if already enabled.
|
||||
R_SUCCEED_IF(m_buffer_sharing_enabled);
|
||||
|
||||
// Ensure we can access shared layers.
|
||||
R_UNLESS(m_manager_display_service != nullptr, VI::ResultOperationFailed);
|
||||
R_UNLESS(m_applet_id != AppletId::Application, VI::ResultPermissionDenied);
|
||||
|
||||
// Create the shared layer.
|
||||
u64 display_id;
|
||||
R_TRY(m_display_service->OpenDisplay(&display_id, VI::DisplayName{"Default"}));
|
||||
R_TRY(m_manager_display_service->CreateSharedLayerSession(m_process, &m_system_shared_buffer_id,
|
||||
&m_system_shared_layer_id, display_id,
|
||||
m_blending_enabled));
|
||||
|
||||
// We succeeded, so set up remaining state.
|
||||
m_buffer_sharing_enabled = true;
|
||||
m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DisplayLayerManager::GetSystemSharedLayerHandle(u64* out_system_shared_buffer_id,
|
||||
u64* out_system_shared_layer_id) {
|
||||
R_TRY(this->IsSystemBufferSharingEnabled());
|
||||
|
||||
*out_system_shared_buffer_id = m_system_shared_buffer_id;
|
||||
*out_system_shared_layer_id = m_system_shared_layer_id;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void DisplayLayerManager::SetWindowVisibility(bool visible) {
|
||||
if (m_visible == visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_visible = visible;
|
||||
|
||||
if (m_manager_display_service) {
|
||||
if (m_system_shared_layer_id) {
|
||||
m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id);
|
||||
}
|
||||
|
||||
for (const auto layer_id : m_managed_display_layers) {
|
||||
m_manager_display_service->SetLayerVisibility(m_visible, layer_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DisplayLayerManager::GetWindowVisibility() const {
|
||||
return m_visible;
|
||||
}
|
||||
|
||||
Result DisplayLayerManager::WriteAppletCaptureBuffer(bool* out_was_written,
|
||||
s32* out_fbshare_layer_index) {
|
||||
R_UNLESS(m_buffer_sharing_enabled, VI::ResultPermissionDenied);
|
||||
R_RETURN(m_display_service->GetContainer()->GetSharedBufferManager()->WriteAppletCaptureBuffer(
|
||||
out_was_written, out_fbshare_layer_index));
|
||||
}
|
||||
|
||||
} // namespace Service::AM
|
@ -1,62 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/am/am_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class KProcess;
|
||||
}
|
||||
|
||||
namespace Service::VI {
|
||||
class IApplicationDisplayService;
|
||||
class IManagerDisplayService;
|
||||
} // namespace Service::VI
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
class DisplayLayerManager {
|
||||
public:
|
||||
explicit DisplayLayerManager();
|
||||
~DisplayLayerManager();
|
||||
|
||||
void Initialize(Core::System& system, Kernel::KProcess* process, AppletId applet_id,
|
||||
LibraryAppletMode mode);
|
||||
void Finalize();
|
||||
|
||||
Result CreateManagedDisplayLayer(u64* out_layer_id);
|
||||
Result CreateManagedDisplaySeparableLayer(u64* out_layer_id, u64* out_recording_layer_id);
|
||||
|
||||
Result IsSystemBufferSharingEnabled();
|
||||
Result GetSystemSharedLayerHandle(u64* out_system_shared_buffer_id,
|
||||
u64* out_system_shared_layer_id);
|
||||
|
||||
void SetWindowVisibility(bool visible);
|
||||
bool GetWindowVisibility() const;
|
||||
|
||||
Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index);
|
||||
|
||||
private:
|
||||
Kernel::KProcess* m_process{};
|
||||
std::shared_ptr<VI::IApplicationDisplayService> m_display_service{};
|
||||
std::shared_ptr<VI::IManagerDisplayService> m_manager_display_service{};
|
||||
std::set<u64> m_managed_display_layers{};
|
||||
std::set<u64> m_managed_display_recording_layers{};
|
||||
u64 m_system_shared_buffer_id{};
|
||||
u64 m_system_shared_layer_id{};
|
||||
AppletId m_applet_id{};
|
||||
bool m_buffer_sharing_enabled{};
|
||||
bool m_blending_enabled{};
|
||||
bool m_visible{true};
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
@ -22,7 +22,7 @@
|
||||
#include "core/hle/service/am/frontend/applet_web_browser.h"
|
||||
#include "core/hle/service/am/service/storage.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/ns/platform_service_manager.h"
|
||||
#include "core/hle/service/ns/iplatform_service_manager.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace Service::AM::Frontend {
|
||||
|
@ -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();
|
||||
}
|
||||
|
59
src/core/hle/service/am/managed_layer_holder.cpp
Normal file
59
src/core/hle/service/am/managed_layer_holder.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/am/managed_layer_holder.h"
|
||||
#include "core/hle/service/nvnflinger/nvnflinger.h"
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
ManagedLayerHolder::ManagedLayerHolder() = default;
|
||||
ManagedLayerHolder::~ManagedLayerHolder() {
|
||||
if (!m_nvnflinger) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& layer : m_managed_display_layers) {
|
||||
m_nvnflinger->DestroyLayer(layer);
|
||||
}
|
||||
|
||||
for (const auto& layer : m_managed_display_recording_layers) {
|
||||
m_nvnflinger->DestroyLayer(layer);
|
||||
}
|
||||
|
||||
m_nvnflinger = nullptr;
|
||||
}
|
||||
|
||||
void ManagedLayerHolder::Initialize(Nvnflinger::Nvnflinger* nvnflinger) {
|
||||
m_nvnflinger = nvnflinger;
|
||||
}
|
||||
|
||||
void ManagedLayerHolder::CreateManagedDisplayLayer(u64* out_layer) {
|
||||
// TODO(Subv): Find out how AM determines the display to use, for now just
|
||||
// create the layer in the Default display.
|
||||
const auto display_id = m_nvnflinger->OpenDisplay("Default");
|
||||
const auto layer_id = m_nvnflinger->CreateLayer(*display_id);
|
||||
|
||||
m_managed_display_layers.emplace(*layer_id);
|
||||
|
||||
*out_layer = *layer_id;
|
||||
}
|
||||
|
||||
void ManagedLayerHolder::CreateManagedDisplaySeparableLayer(u64* out_layer,
|
||||
u64* out_recording_layer) {
|
||||
// TODO(Subv): Find out how AM determines the display to use, for now just
|
||||
// create the layer in the Default display.
|
||||
// This calls nn::vi::CreateRecordingLayer() which creates another layer.
|
||||
// Currently we do not support more than 1 layer per display, output 1 layer id for now.
|
||||
// Outputting 1 layer id instead of the expected 2 has not been observed to cause any adverse
|
||||
// side effects.
|
||||
// TODO: Support multiple layers
|
||||
const auto display_id = m_nvnflinger->OpenDisplay("Default");
|
||||
const auto layer_id = m_nvnflinger->CreateLayer(*display_id);
|
||||
|
||||
m_managed_display_layers.emplace(*layer_id);
|
||||
|
||||
*out_layer = *layer_id;
|
||||
*out_recording_layer = 0;
|
||||
}
|
||||
|
||||
} // namespace Service::AM
|
32
src/core/hle/service/am/managed_layer_holder.h
Normal file
32
src/core/hle/service/am/managed_layer_holder.h
Normal file
@ -0,0 +1,32 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Service::Nvnflinger {
|
||||
class Nvnflinger;
|
||||
}
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
class ManagedLayerHolder {
|
||||
public:
|
||||
ManagedLayerHolder();
|
||||
~ManagedLayerHolder();
|
||||
|
||||
void Initialize(Nvnflinger::Nvnflinger* nvnflinger);
|
||||
void CreateManagedDisplayLayer(u64* out_layer);
|
||||
void CreateManagedDisplaySeparableLayer(u64* out_layer, u64* out_recording_layer);
|
||||
|
||||
private:
|
||||
Nvnflinger::Nvnflinger* m_nvnflinger{};
|
||||
std::set<u64> m_managed_display_layers{};
|
||||
std::set<u64> m_managed_display_recording_layers{};
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
@ -10,8 +10,9 @@
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
IAllSystemAppletProxiesService::IAllSystemAppletProxiesService(Core::System& system_)
|
||||
: ServiceFramework{system_, "appletAE"} {
|
||||
IAllSystemAppletProxiesService::IAllSystemAppletProxiesService(Core::System& system_,
|
||||
Nvnflinger::Nvnflinger& nvnflinger)
|
||||
: ServiceFramework{system_, "appletAE"}, m_nvnflinger{nvnflinger} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{100, D<&IAllSystemAppletProxiesService::OpenSystemAppletProxy>, "OpenSystemAppletProxy"},
|
||||
@ -36,8 +37,8 @@ Result IAllSystemAppletProxiesService::OpenSystemAppletProxy(
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
if (const auto applet = this->GetAppletFromProcessId(pid); applet) {
|
||||
*out_system_applet_proxy =
|
||||
std::make_shared<ISystemAppletProxy>(system, applet, process_handle.Get());
|
||||
*out_system_applet_proxy = std::make_shared<ISystemAppletProxy>(
|
||||
system, applet, process_handle.Get(), m_nvnflinger);
|
||||
R_SUCCEED();
|
||||
} else {
|
||||
UNIMPLEMENTED();
|
||||
@ -52,8 +53,8 @@ Result IAllSystemAppletProxiesService::OpenLibraryAppletProxy(
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
if (const auto applet = this->GetAppletFromProcessId(pid); applet) {
|
||||
*out_library_applet_proxy =
|
||||
std::make_shared<ILibraryAppletProxy>(system, applet, process_handle.Get());
|
||||
*out_library_applet_proxy = std::make_shared<ILibraryAppletProxy>(
|
||||
system, applet, process_handle.Get(), m_nvnflinger);
|
||||
R_SUCCEED();
|
||||
} else {
|
||||
UNIMPLEMENTED();
|
||||
|
@ -8,6 +8,10 @@
|
||||
|
||||
namespace Service {
|
||||
|
||||
namespace Nvnflinger {
|
||||
class Nvnflinger;
|
||||
}
|
||||
|
||||
namespace AM {
|
||||
|
||||
struct Applet;
|
||||
@ -18,7 +22,8 @@ class ISystemAppletProxy;
|
||||
class IAllSystemAppletProxiesService final
|
||||
: public ServiceFramework<IAllSystemAppletProxiesService> {
|
||||
public:
|
||||
explicit IAllSystemAppletProxiesService(Core::System& system_);
|
||||
explicit IAllSystemAppletProxiesService(Core::System& system_,
|
||||
Nvnflinger::Nvnflinger& nvnflinger);
|
||||
~IAllSystemAppletProxiesService() override;
|
||||
|
||||
private:
|
||||
@ -35,6 +40,7 @@ private:
|
||||
|
||||
private:
|
||||
std::shared_ptr<Applet> GetAppletFromProcessId(ProcessId pid);
|
||||
Nvnflinger::Nvnflinger& m_nvnflinger;
|
||||
};
|
||||
|
||||
} // namespace AM
|
||||
|
@ -15,9 +15,7 @@
|
||||
#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/ns/ns.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
namespace Service::AM {
|
||||
@ -42,7 +40,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"},
|
||||
@ -164,13 +162,11 @@ Result IApplicationFunctions::GetDesiredLanguage(Out<u64> out_language_code) {
|
||||
|
||||
// Call IApplicationManagerInterface implementation.
|
||||
auto& service_manager = system.ServiceManager();
|
||||
auto ns_am2 = service_manager.GetService<NS::IServiceGetterInterface>("ns:am2");
|
||||
|
||||
std::shared_ptr<NS::IApplicationManagerInterface> app_man;
|
||||
R_TRY(ns_am2->GetApplicationManagerInterface(&app_man));
|
||||
auto ns_am2 = service_manager.GetService<NS::NS>("ns:am2");
|
||||
auto app_man = ns_am2->GetApplicationManagerInterface();
|
||||
|
||||
// Get desired application language
|
||||
NS::ApplicationLanguage desired_language{};
|
||||
u8 desired_language{};
|
||||
R_TRY(app_man->GetApplicationDesiredLanguage(&desired_language, supported_languages));
|
||||
|
||||
// Convert to settings language code.
|
||||
@ -271,22 +267,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);
|
||||
|
@ -17,9 +17,9 @@
|
||||
namespace Service::AM {
|
||||
|
||||
IApplicationProxy::IApplicationProxy(Core::System& system_, std::shared_ptr<Applet> applet,
|
||||
Kernel::KProcess* process)
|
||||
: ServiceFramework{system_, "IApplicationProxy"}, m_process{process}, m_applet{
|
||||
std::move(applet)} {
|
||||
Kernel::KProcess* process, Nvnflinger::Nvnflinger& nvnflinger)
|
||||
: ServiceFramework{system_, "IApplicationProxy"},
|
||||
m_nvnflinger{nvnflinger}, m_process{process}, m_applet{std::move(applet)} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, D<&IApplicationProxy::GetCommonStateGetter>, "GetCommonStateGetter"},
|
||||
@ -77,7 +77,8 @@ Result IApplicationProxy::GetWindowController(
|
||||
Result IApplicationProxy::GetSelfController(
|
||||
Out<SharedPointer<ISelfController>> out_self_controller) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
*out_self_controller = std::make_shared<ISelfController>(system, m_applet, m_process);
|
||||
*out_self_controller =
|
||||
std::make_shared<ISelfController>(system, m_applet, m_process, m_nvnflinger);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ class IWindowController;
|
||||
class IApplicationProxy final : public ServiceFramework<IApplicationProxy> {
|
||||
public:
|
||||
explicit IApplicationProxy(Core::System& system_, std::shared_ptr<Applet> applet,
|
||||
Kernel::KProcess* process);
|
||||
Kernel::KProcess* process, Nvnflinger::Nvnflinger& nvnflinger);
|
||||
~IApplicationProxy();
|
||||
|
||||
private:
|
||||
@ -40,6 +40,7 @@ private:
|
||||
Out<SharedPointer<IApplicationFunctions>> out_application_functions);
|
||||
|
||||
private:
|
||||
Nvnflinger::Nvnflinger& m_nvnflinger;
|
||||
Kernel::KProcess* const m_process;
|
||||
const std::shared_ptr<Applet> m_applet;
|
||||
};
|
||||
|
@ -10,8 +10,9 @@
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
IApplicationProxyService::IApplicationProxyService(Core::System& system_)
|
||||
: ServiceFramework{system_, "appletOE"} {
|
||||
IApplicationProxyService::IApplicationProxyService(Core::System& system_,
|
||||
Nvnflinger::Nvnflinger& nvnflinger)
|
||||
: ServiceFramework{system_, "appletOE"}, m_nvnflinger{nvnflinger} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, D<&IApplicationProxyService::OpenApplicationProxy>, "OpenApplicationProxy"},
|
||||
};
|
||||
@ -27,7 +28,7 @@ Result IApplicationProxyService::OpenApplicationProxy(
|
||||
|
||||
if (const auto applet = this->GetAppletFromProcessId(pid)) {
|
||||
*out_application_proxy =
|
||||
std::make_shared<IApplicationProxy>(system, applet, process_handle.Get());
|
||||
std::make_shared<IApplicationProxy>(system, applet, process_handle.Get(), m_nvnflinger);
|
||||
R_SUCCEED();
|
||||
} else {
|
||||
UNIMPLEMENTED();
|
||||
|
@ -8,6 +8,10 @@
|
||||
|
||||
namespace Service {
|
||||
|
||||
namespace Nvnflinger {
|
||||
class Nvnflinger;
|
||||
}
|
||||
|
||||
namespace AM {
|
||||
|
||||
struct Applet;
|
||||
@ -15,7 +19,7 @@ class IApplicationProxy;
|
||||
|
||||
class IApplicationProxyService final : public ServiceFramework<IApplicationProxyService> {
|
||||
public:
|
||||
explicit IApplicationProxyService(Core::System& system_);
|
||||
explicit IApplicationProxyService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger);
|
||||
~IApplicationProxyService() override;
|
||||
|
||||
private:
|
||||
@ -24,6 +28,7 @@ private:
|
||||
|
||||
private:
|
||||
std::shared_ptr<Applet> GetAppletFromProcessId(ProcessId pid);
|
||||
Nvnflinger::Nvnflinger& m_nvnflinger;
|
||||
};
|
||||
|
||||
} // namespace AM
|
||||
|
@ -69,7 +69,7 @@ Result IDisplayController::ClearCaptureBuffer(bool unknown0, s32 fbshare_layer_i
|
||||
Result IDisplayController::AcquireLastForegroundCaptureSharedBuffer(
|
||||
Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written,
|
||||
R_RETURN(applet->system_buffer_manager.WriteAppletCaptureBuffer(out_was_written,
|
||||
out_fbshare_layer_index));
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ Result IDisplayController::ReleaseLastForegroundCaptureSharedBuffer() {
|
||||
Result IDisplayController::AcquireCallerAppletCaptureSharedBuffer(
|
||||
Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written,
|
||||
R_RETURN(applet->system_buffer_manager.WriteAppletCaptureBuffer(out_was_written,
|
||||
out_fbshare_layer_index));
|
||||
}
|
||||
|
||||
@ -93,7 +93,7 @@ Result IDisplayController::ReleaseCallerAppletCaptureSharedBuffer() {
|
||||
Result IDisplayController::AcquireLastApplicationCaptureSharedBuffer(
|
||||
Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written,
|
||||
R_RETURN(applet->system_buffer_manager.WriteAppletCaptureBuffer(out_was_written,
|
||||
out_fbshare_layer_index));
|
||||
}
|
||||
|
||||
|
@ -135,7 +135,7 @@ std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
|
||||
case LibraryAppletMode::AllForegroundInitiallyHidden:
|
||||
applet->hid_registration.EnableAppletToGetInput(false);
|
||||
applet->focus_state = FocusState::NotInFocus;
|
||||
applet->display_layer_manager.SetWindowVisibility(false);
|
||||
applet->system_buffer_manager.SetWindowVisibility(false);
|
||||
applet->message_queue.PushMessage(AppletMessage::ChangeIntoBackground);
|
||||
break;
|
||||
}
|
||||
|
@ -19,9 +19,10 @@
|
||||
namespace Service::AM {
|
||||
|
||||
ILibraryAppletProxy::ILibraryAppletProxy(Core::System& system_, std::shared_ptr<Applet> applet,
|
||||
Kernel::KProcess* process)
|
||||
: ServiceFramework{system_, "ILibraryAppletProxy"}, m_process{process}, m_applet{
|
||||
std::move(applet)} {
|
||||
Kernel::KProcess* process,
|
||||
Nvnflinger::Nvnflinger& nvnflinger)
|
||||
: ServiceFramework{system_, "ILibraryAppletProxy"},
|
||||
m_nvnflinger{nvnflinger}, m_process{process}, m_applet{std::move(applet)} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, D<&ILibraryAppletProxy::GetCommonStateGetter>, "GetCommonStateGetter"},
|
||||
@ -82,7 +83,8 @@ Result ILibraryAppletProxy::GetWindowController(
|
||||
Result ILibraryAppletProxy::GetSelfController(
|
||||
Out<SharedPointer<ISelfController>> out_self_controller) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
*out_self_controller = std::make_shared<ISelfController>(system, m_applet, m_process);
|
||||
*out_self_controller =
|
||||
std::make_shared<ISelfController>(system, m_applet, m_process, m_nvnflinger);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ class IWindowController;
|
||||
class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> {
|
||||
public:
|
||||
explicit ILibraryAppletProxy(Core::System& system_, std::shared_ptr<Applet> applet,
|
||||
Kernel::KProcess* process);
|
||||
Kernel::KProcess* process, Nvnflinger::Nvnflinger& nvnflinger);
|
||||
~ILibraryAppletProxy();
|
||||
|
||||
private:
|
||||
@ -47,6 +47,7 @@ private:
|
||||
Result GetGlobalStateController(
|
||||
Out<SharedPointer<IGlobalStateController>> out_global_state_controller);
|
||||
|
||||
Nvnflinger::Nvnflinger& m_nvnflinger;
|
||||
Kernel::KProcess* const m_process;
|
||||
const std::shared_ptr<Applet> m_applet;
|
||||
};
|
||||
|
@ -14,8 +14,7 @@
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/filesystem/filesystem.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/ns/ns.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
namespace Service::AM {
|
||||
@ -257,13 +256,11 @@ Result ILibraryAppletSelfAccessor::GetMainAppletApplicationDesiredLanguage(
|
||||
|
||||
// Call IApplicationManagerInterface implementation.
|
||||
auto& service_manager = system.ServiceManager();
|
||||
auto ns_am2 = service_manager.GetService<NS::IServiceGetterInterface>("ns:am2");
|
||||
|
||||
std::shared_ptr<NS::IApplicationManagerInterface> app_man;
|
||||
R_TRY(ns_am2->GetApplicationManagerInterface(&app_man));
|
||||
auto ns_am2 = service_manager.GetService<NS::NS>("ns:am2");
|
||||
auto app_man = ns_am2->GetApplicationManagerInterface();
|
||||
|
||||
// Get desired application language
|
||||
NS::ApplicationLanguage desired_language{};
|
||||
u8 desired_language{};
|
||||
R_TRY(app_man->GetApplicationDesiredLanguage(&desired_language, supported_languages));
|
||||
|
||||
// Convert to settings language code.
|
||||
@ -287,17 +284,17 @@ Result ILibraryAppletSelfAccessor::GetCurrentApplicationId(Out<u64> out_applicat
|
||||
}
|
||||
|
||||
Result ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(
|
||||
Out<bool> out_can_select_any_user, Out<s32> out_users_count,
|
||||
Out<bool> out_no_users_available, Out<s32> out_users_count,
|
||||
OutArray<Common::UUID, BufferAttr_HipcMapAlias> out_users) {
|
||||
const Service::Account::ProfileManager manager{};
|
||||
|
||||
*out_can_select_any_user = false;
|
||||
*out_no_users_available = true;
|
||||
*out_users_count = -1;
|
||||
|
||||
LOG_INFO(Service_AM, "called");
|
||||
|
||||
if (manager.GetUserCount() > 0) {
|
||||
*out_can_select_any_user = true;
|
||||
*out_no_users_available = false;
|
||||
*out_users_count = static_cast<s32>(manager.GetUserCount());
|
||||
|
||||
const auto users = manager.GetAllUsers();
|
||||
|
@ -71,7 +71,7 @@ private:
|
||||
ErrorCode error_code, InLargeData<ErrorContext, BufferAttr_HipcMapAlias> error_context);
|
||||
Result GetMainAppletApplicationDesiredLanguage(Out<u64> out_desired_language);
|
||||
Result GetCurrentApplicationId(Out<u64> out_application_id);
|
||||
Result GetMainAppletAvailableUsers(Out<bool> out_can_select_any_user, Out<s32> out_users_count,
|
||||
Result GetMainAppletAvailableUsers(Out<bool> out_no_users_available, Out<s32> out_users_count,
|
||||
OutArray<Common::UUID, BufferAttr_HipcMapAlias> out_users);
|
||||
Result ShouldSetGpuTimeSliceManually(Out<bool> out_should_set_gpu_time_slice_manually);
|
||||
Result Cmd160(Out<u64> out_unknown0);
|
||||
|
@ -15,9 +15,9 @@
|
||||
namespace Service::AM {
|
||||
|
||||
ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet> applet,
|
||||
Kernel::KProcess* process)
|
||||
: ServiceFramework{system_, "ISelfController"}, m_process{process}, m_applet{
|
||||
std::move(applet)} {
|
||||
Kernel::KProcess* process, Nvnflinger::Nvnflinger& nvnflinger)
|
||||
: ServiceFramework{system_, "ISelfController"},
|
||||
m_nvnflinger{nvnflinger}, m_process{process}, m_applet{std::move(applet)} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, D<&ISelfController::Exit>, "Exit"},
|
||||
@ -72,16 +72,9 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet>
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
m_applet->display_layer_manager.Initialize(system, m_process, m_applet->applet_id,
|
||||
m_applet->library_applet_mode);
|
||||
}
|
||||
|
||||
ISelfController::~ISelfController() {
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
m_applet->display_layer_manager.Finalize();
|
||||
}
|
||||
ISelfController::~ISelfController() = default;
|
||||
|
||||
Result ISelfController::Exit() {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
@ -219,42 +212,48 @@ Result ISelfController::SetAlbumImageOrientation(
|
||||
|
||||
Result ISelfController::IsSystemBufferSharingEnabled() {
|
||||
LOG_INFO(Service_AM, "called");
|
||||
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
R_RETURN(m_applet->display_layer_manager.IsSystemBufferSharingEnabled());
|
||||
R_SUCCEED_IF(m_applet->system_buffer_manager.Initialize(
|
||||
&m_nvnflinger, m_process, m_applet->applet_id, m_applet->library_applet_mode));
|
||||
R_THROW(VI::ResultOperationFailed);
|
||||
}
|
||||
|
||||
Result ISelfController::GetSystemSharedBufferHandle(Out<u64> out_buffer_id) {
|
||||
LOG_INFO(Service_AM, "called");
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
R_TRY(this->IsSystemBufferSharingEnabled());
|
||||
|
||||
u64 layer_id;
|
||||
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
R_RETURN(m_applet->display_layer_manager.GetSystemSharedLayerHandle(out_buffer_id, &layer_id));
|
||||
m_applet->system_buffer_manager.GetSystemSharedLayerHandle(out_buffer_id, &layer_id);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ISelfController::GetSystemSharedLayerHandle(Out<u64> out_buffer_id, Out<u64> out_layer_id) {
|
||||
LOG_INFO(Service_AM, "called");
|
||||
LOG_INFO(Service_AM, "(STUBBED) called");
|
||||
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
R_RETURN(
|
||||
m_applet->display_layer_manager.GetSystemSharedLayerHandle(out_buffer_id, out_layer_id));
|
||||
R_TRY(this->IsSystemBufferSharingEnabled());
|
||||
|
||||
m_applet->system_buffer_manager.GetSystemSharedLayerHandle(out_buffer_id, out_layer_id);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ISelfController::CreateManagedDisplayLayer(Out<u64> out_layer_id) {
|
||||
LOG_INFO(Service_AM, "called");
|
||||
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
R_RETURN(m_applet->display_layer_manager.CreateManagedDisplayLayer(out_layer_id));
|
||||
m_applet->managed_layer_holder.Initialize(&m_nvnflinger);
|
||||
m_applet->managed_layer_holder.CreateManagedDisplayLayer(out_layer_id);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ISelfController::CreateManagedDisplaySeparableLayer(Out<u64> out_layer_id,
|
||||
Out<u64> out_recording_layer_id) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
R_RETURN(m_applet->display_layer_manager.CreateManagedDisplaySeparableLayer(
|
||||
out_layer_id, out_recording_layer_id));
|
||||
m_applet->managed_layer_holder.Initialize(&m_nvnflinger);
|
||||
m_applet->managed_layer_holder.CreateManagedDisplaySeparableLayer(out_layer_id,
|
||||
out_recording_layer_id);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ISelfController::SetHandlesRequestToDisplay(bool enable) {
|
||||
|
@ -23,7 +23,7 @@ struct Applet;
|
||||
class ISelfController final : public ServiceFramework<ISelfController> {
|
||||
public:
|
||||
explicit ISelfController(Core::System& system_, std::shared_ptr<Applet> applet,
|
||||
Kernel::KProcess* process);
|
||||
Kernel::KProcess* process, Nvnflinger::Nvnflinger& nvnflinger);
|
||||
~ISelfController() override;
|
||||
|
||||
private:
|
||||
@ -64,6 +64,7 @@ private:
|
||||
Result SaveCurrentScreenshot(Capture::AlbumReportOption album_report_option);
|
||||
Result SetRecordVolumeMuted(bool muted);
|
||||
|
||||
Nvnflinger::Nvnflinger& m_nvnflinger;
|
||||
Kernel::KProcess* const m_process;
|
||||
const std::shared_ptr<Applet> m_applet;
|
||||
};
|
||||
|
@ -19,9 +19,10 @@
|
||||
namespace Service::AM {
|
||||
|
||||
ISystemAppletProxy::ISystemAppletProxy(Core::System& system_, std::shared_ptr<Applet> applet,
|
||||
Kernel::KProcess* process)
|
||||
: ServiceFramework{system_, "ISystemAppletProxy"}, m_process{process}, m_applet{
|
||||
std::move(applet)} {
|
||||
Kernel::KProcess* process,
|
||||
Nvnflinger::Nvnflinger& nvnflinger)
|
||||
: ServiceFramework{system_, "ISystemAppletProxy"},
|
||||
m_nvnflinger{nvnflinger}, m_process{process}, m_applet{std::move(applet)} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, D<&ISystemAppletProxy::GetCommonStateGetter>, "GetCommonStateGetter"},
|
||||
@ -82,7 +83,8 @@ Result ISystemAppletProxy::GetWindowController(
|
||||
Result ISystemAppletProxy::GetSelfController(
|
||||
Out<SharedPointer<ISelfController>> out_self_controller) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
*out_self_controller = std::make_shared<ISelfController>(system, m_applet, m_process);
|
||||
*out_self_controller =
|
||||
std::make_shared<ISelfController>(system, m_applet, m_process, m_nvnflinger);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ class IWindowController;
|
||||
class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> {
|
||||
public:
|
||||
explicit ISystemAppletProxy(Core::System& system, std::shared_ptr<Applet> applet,
|
||||
Kernel::KProcess* process);
|
||||
Kernel::KProcess* process, Nvnflinger::Nvnflinger& nvnflinger);
|
||||
~ISystemAppletProxy();
|
||||
|
||||
private:
|
||||
@ -46,6 +46,7 @@ private:
|
||||
Result GetGlobalStateController(
|
||||
Out<SharedPointer<IGlobalStateController>> out_global_state_controller);
|
||||
|
||||
Nvnflinger::Nvnflinger& m_nvnflinger;
|
||||
Kernel::KProcess* const m_process;
|
||||
const std::shared_ptr<Applet> m_applet;
|
||||
};
|
||||
|
@ -63,7 +63,7 @@ Result IWindowController::RejectToChangeIntoBackground() {
|
||||
}
|
||||
|
||||
Result IWindowController::SetAppletWindowVisibility(bool visible) {
|
||||
m_applet->display_layer_manager.SetWindowVisibility(visible);
|
||||
m_applet->system_buffer_manager.SetWindowVisibility(visible);
|
||||
m_applet->hid_registration.EnableAppletToGetInput(visible);
|
||||
|
||||
if (visible) {
|
||||
|
80
src/core/hle/service/am/system_buffer_manager.cpp
Normal file
80
src/core/hle/service/am/system_buffer_manager.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/am/system_buffer_manager.h"
|
||||
#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
|
||||
#include "core/hle/service/nvnflinger/nvnflinger.h"
|
||||
#include "core/hle/service/vi/vi_results.h"
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
SystemBufferManager::SystemBufferManager() = default;
|
||||
|
||||
SystemBufferManager::~SystemBufferManager() {
|
||||
if (!m_nvnflinger) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up shared layers.
|
||||
if (m_buffer_sharing_enabled) {
|
||||
m_nvnflinger->GetSystemBufferManager().Finalize(m_process);
|
||||
}
|
||||
}
|
||||
|
||||
bool SystemBufferManager::Initialize(Nvnflinger::Nvnflinger* nvnflinger, Kernel::KProcess* process,
|
||||
AppletId applet_id, LibraryAppletMode mode) {
|
||||
if (m_nvnflinger) {
|
||||
return m_buffer_sharing_enabled;
|
||||
}
|
||||
|
||||
m_process = process;
|
||||
m_nvnflinger = nvnflinger;
|
||||
m_buffer_sharing_enabled = false;
|
||||
m_system_shared_buffer_id = 0;
|
||||
m_system_shared_layer_id = 0;
|
||||
|
||||
if (applet_id <= AppletId::Application) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Nvnflinger::LayerBlending blending = Nvnflinger::LayerBlending::None;
|
||||
if (mode == LibraryAppletMode::PartialForeground ||
|
||||
mode == LibraryAppletMode::PartialForegroundIndirectDisplay) {
|
||||
blending = Nvnflinger::LayerBlending::Coverage;
|
||||
}
|
||||
|
||||
const auto display_id = m_nvnflinger->OpenDisplay("Default").value();
|
||||
const auto res = m_nvnflinger->GetSystemBufferManager().Initialize(
|
||||
m_process, &m_system_shared_buffer_id, &m_system_shared_layer_id, display_id, blending);
|
||||
|
||||
if (res.IsSuccess()) {
|
||||
m_buffer_sharing_enabled = true;
|
||||
m_nvnflinger->SetLayerVisibility(m_system_shared_layer_id, m_visible);
|
||||
}
|
||||
|
||||
return m_buffer_sharing_enabled;
|
||||
}
|
||||
|
||||
void SystemBufferManager::SetWindowVisibility(bool visible) {
|
||||
if (m_visible == visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_visible = visible;
|
||||
|
||||
if (m_nvnflinger) {
|
||||
m_nvnflinger->SetLayerVisibility(m_system_shared_layer_id, m_visible);
|
||||
}
|
||||
}
|
||||
|
||||
Result SystemBufferManager::WriteAppletCaptureBuffer(bool* out_was_written,
|
||||
s32* out_fbshare_layer_index) {
|
||||
if (!m_buffer_sharing_enabled) {
|
||||
return VI::ResultPermissionDenied;
|
||||
}
|
||||
|
||||
return m_nvnflinger->GetSystemBufferManager().WriteAppletCaptureBuffer(out_was_written,
|
||||
out_fbshare_layer_index);
|
||||
}
|
||||
|
||||
} // namespace Service::AM
|
52
src/core/hle/service/am/system_buffer_manager.h
Normal file
52
src/core/hle/service/am/system_buffer_manager.h
Normal file
@ -0,0 +1,52 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
#include "core/hle/service/am/am_types.h"
|
||||
|
||||
namespace Kernel {
|
||||
class KProcess;
|
||||
}
|
||||
|
||||
namespace Service::Nvnflinger {
|
||||
class Nvnflinger;
|
||||
}
|
||||
|
||||
union Result;
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
class SystemBufferManager {
|
||||
public:
|
||||
SystemBufferManager();
|
||||
~SystemBufferManager();
|
||||
|
||||
bool Initialize(Nvnflinger::Nvnflinger* flinger, Kernel::KProcess* process, AppletId applet_id,
|
||||
LibraryAppletMode mode);
|
||||
|
||||
void GetSystemSharedLayerHandle(u64* out_system_shared_buffer_id,
|
||||
u64* out_system_shared_layer_id) {
|
||||
*out_system_shared_buffer_id = m_system_shared_buffer_id;
|
||||
*out_system_shared_layer_id = m_system_shared_layer_id;
|
||||
}
|
||||
|
||||
void SetWindowVisibility(bool visible);
|
||||
|
||||
Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index);
|
||||
|
||||
private:
|
||||
Kernel::KProcess* m_process{};
|
||||
Nvnflinger::Nvnflinger* m_nvnflinger{};
|
||||
bool m_buffer_sharing_enabled{};
|
||||
bool m_visible{true};
|
||||
u64 m_system_shared_buffer_id{};
|
||||
u64 m_system_shared_layer_id{};
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
201
src/core/hle/service/audio/audctl.cpp
Normal file
201
src/core/hle/service/audio/audctl.cpp
Normal file
@ -0,0 +1,201 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/audio/audctl.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/set/system_settings_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetTargetVolume"},
|
||||
{1, nullptr, "SetTargetVolume"},
|
||||
{2, &AudCtl::GetTargetVolumeMin, "GetTargetVolumeMin"},
|
||||
{3, &AudCtl::GetTargetVolumeMax, "GetTargetVolumeMax"},
|
||||
{4, nullptr, "IsTargetMute"},
|
||||
{5, nullptr, "SetTargetMute"},
|
||||
{6, nullptr, "IsTargetConnected"},
|
||||
{7, nullptr, "SetDefaultTarget"},
|
||||
{8, nullptr, "GetDefaultTarget"},
|
||||
{9, &AudCtl::GetAudioOutputMode, "GetAudioOutputMode"},
|
||||
{10, &AudCtl::SetAudioOutputMode, "SetAudioOutputMode"},
|
||||
{11, nullptr, "SetForceMutePolicy"},
|
||||
{12, &AudCtl::GetForceMutePolicy, "GetForceMutePolicy"},
|
||||
{13, &AudCtl::GetOutputModeSetting, "GetOutputModeSetting"},
|
||||
{14, &AudCtl::SetOutputModeSetting, "SetOutputModeSetting"},
|
||||
{15, nullptr, "SetOutputTarget"},
|
||||
{16, nullptr, "SetInputTargetForceEnabled"},
|
||||
{17, &AudCtl::SetHeadphoneOutputLevelMode, "SetHeadphoneOutputLevelMode"},
|
||||
{18, &AudCtl::GetHeadphoneOutputLevelMode, "GetHeadphoneOutputLevelMode"},
|
||||
{19, nullptr, "AcquireAudioVolumeUpdateEventForPlayReport"},
|
||||
{20, nullptr, "AcquireAudioOutputDeviceUpdateEventForPlayReport"},
|
||||
{21, nullptr, "GetAudioOutputTargetForPlayReport"},
|
||||
{22, nullptr, "NotifyHeadphoneVolumeWarningDisplayedEvent"},
|
||||
{23, nullptr, "SetSystemOutputMasterVolume"},
|
||||
{24, nullptr, "GetSystemOutputMasterVolume"},
|
||||
{25, nullptr, "GetAudioVolumeDataForPlayReport"},
|
||||
{26, nullptr, "UpdateHeadphoneSettings"},
|
||||
{27, nullptr, "SetVolumeMappingTableForDev"},
|
||||
{28, nullptr, "GetAudioOutputChannelCountForPlayReport"},
|
||||
{29, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"},
|
||||
{30, &AudCtl::SetSpeakerAutoMuteEnabled, "SetSpeakerAutoMuteEnabled"},
|
||||
{31, &AudCtl::IsSpeakerAutoMuteEnabled, "IsSpeakerAutoMuteEnabled"},
|
||||
{32, nullptr, "GetActiveOutputTarget"},
|
||||
{33, nullptr, "GetTargetDeviceInfo"},
|
||||
{34, nullptr, "AcquireTargetNotification"},
|
||||
{35, nullptr, "SetHearingProtectionSafeguardTimerRemainingTimeForDebug"},
|
||||
{36, nullptr, "GetHearingProtectionSafeguardTimerRemainingTimeForDebug"},
|
||||
{37, nullptr, "SetHearingProtectionSafeguardEnabled"},
|
||||
{38, nullptr, "IsHearingProtectionSafeguardEnabled"},
|
||||
{39, nullptr, "IsHearingProtectionSafeguardMonitoringOutputForDebug"},
|
||||
{40, nullptr, "GetSystemInformationForDebug"},
|
||||
{41, nullptr, "SetVolumeButtonLongPressTime"},
|
||||
{42, nullptr, "SetNativeVolumeForDebug"},
|
||||
{10000, nullptr, "NotifyAudioOutputTargetForPlayReport"},
|
||||
{10001, nullptr, "NotifyAudioOutputChannelCountForPlayReport"},
|
||||
{10002, nullptr, "NotifyUnsupportedUsbOutputDeviceAttachedForPlayReport"},
|
||||
{10100, nullptr, "GetAudioVolumeDataForPlayReport"},
|
||||
{10101, nullptr, "BindAudioVolumeUpdateEventForPlayReport"},
|
||||
{10102, nullptr, "BindAudioOutputTargetUpdateEventForPlayReport"},
|
||||
{10103, nullptr, "GetAudioOutputTargetForPlayReport"},
|
||||
{10104, nullptr, "GetAudioOutputChannelCountForPlayReport"},
|
||||
{10105, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"},
|
||||
{10106, nullptr, "GetDefaultAudioOutputTargetForPlayReport"},
|
||||
{50000, nullptr, "SetAnalogInputBoostGainForPrototyping"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
|
||||
m_set_sys =
|
||||
system.ServiceManager().GetService<Service::Set::ISystemSettingsServer>("set:sys", true);
|
||||
}
|
||||
|
||||
AudCtl::~AudCtl() = default;
|
||||
|
||||
void AudCtl::GetTargetVolumeMin(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called.");
|
||||
|
||||
// This service function is currently hardcoded on the
|
||||
// actual console to this value (as of 8.0.0).
|
||||
constexpr s32 target_min_volume = 0;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(target_min_volume);
|
||||
}
|
||||
|
||||
void AudCtl::GetTargetVolumeMax(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called.");
|
||||
|
||||
// This service function is currently hardcoded on the
|
||||
// actual console to this value (as of 8.0.0).
|
||||
constexpr s32 target_max_volume = 15;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(target_max_volume);
|
||||
}
|
||||
|
||||
void AudCtl::GetAudioOutputMode(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto target{rp.PopEnum<Set::AudioOutputModeTarget>()};
|
||||
|
||||
Set::AudioOutputMode output_mode{};
|
||||
const auto result = m_set_sys->GetAudioOutputMode(&output_mode, target);
|
||||
|
||||
LOG_INFO(Service_SET, "called, target={}, output_mode={}", target, output_mode);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(result);
|
||||
rb.PushEnum(output_mode);
|
||||
}
|
||||
|
||||
void AudCtl::SetAudioOutputMode(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto target{rp.PopEnum<Set::AudioOutputModeTarget>()};
|
||||
const auto output_mode{rp.PopEnum<Set::AudioOutputMode>()};
|
||||
|
||||
const auto result = m_set_sys->SetAudioOutputMode(target, output_mode);
|
||||
|
||||
LOG_INFO(Service_SET, "called, target={}, output_mode={}", target, output_mode);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void AudCtl::GetForceMutePolicy(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Audio, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(ForceMutePolicy::Disable);
|
||||
}
|
||||
|
||||
void AudCtl::GetOutputModeSetting(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto target{rp.PopEnum<Set::AudioOutputModeTarget>()};
|
||||
|
||||
LOG_WARNING(Audio, "(STUBBED) called, target={}", target);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(Set::AudioOutputMode::ch_7_1);
|
||||
}
|
||||
|
||||
void AudCtl::SetOutputModeSetting(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto target{rp.PopEnum<Set::AudioOutputModeTarget>()};
|
||||
const auto output_mode{rp.PopEnum<Set::AudioOutputMode>()};
|
||||
|
||||
LOG_INFO(Service_SET, "called, target={}, output_mode={}", target, output_mode);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void AudCtl::SetHeadphoneOutputLevelMode(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Audio, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void AudCtl::GetHeadphoneOutputLevelMode(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Audio, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(HeadphoneOutputLevelMode::Normal);
|
||||
}
|
||||
|
||||
void AudCtl::SetSpeakerAutoMuteEnabled(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto is_speaker_auto_mute_enabled{rp.Pop<bool>()};
|
||||
|
||||
LOG_WARNING(Audio, "(STUBBED) called, is_speaker_auto_mute_enabled={}",
|
||||
is_speaker_auto_mute_enabled);
|
||||
|
||||
const auto result = m_set_sys->SetSpeakerAutoMuteFlag(is_speaker_auto_mute_enabled);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void AudCtl::IsSpeakerAutoMuteEnabled(HLERequestContext& ctx) {
|
||||
bool is_speaker_auto_mute_enabled{};
|
||||
const auto result = m_set_sys->GetSpeakerAutoMuteFlag(&is_speaker_auto_mute_enabled);
|
||||
|
||||
LOG_WARNING(Audio, "(STUBBED) called, is_speaker_auto_mute_enabled={}",
|
||||
is_speaker_auto_mute_enabled);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(result);
|
||||
rb.Push<u8>(is_speaker_auto_mute_enabled);
|
||||
}
|
||||
|
||||
} // namespace Service::Audio
|
50
src/core/hle/service/audio/audctl.h
Normal file
50
src/core/hle/service/audio/audctl.h
Normal file
@ -0,0 +1,50 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::Set {
|
||||
class ISystemSettingsServer;
|
||||
}
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
class AudCtl final : public ServiceFramework<AudCtl> {
|
||||
public:
|
||||
explicit AudCtl(Core::System& system_);
|
||||
~AudCtl() override;
|
||||
|
||||
private:
|
||||
enum class ForceMutePolicy {
|
||||
Disable,
|
||||
SpeakerMuteOnHeadphoneUnplugged,
|
||||
};
|
||||
|
||||
enum class HeadphoneOutputLevelMode {
|
||||
Normal,
|
||||
HighPower,
|
||||
};
|
||||
|
||||
void GetTargetVolumeMin(HLERequestContext& ctx);
|
||||
void GetTargetVolumeMax(HLERequestContext& ctx);
|
||||
void GetAudioOutputMode(HLERequestContext& ctx);
|
||||
void SetAudioOutputMode(HLERequestContext& ctx);
|
||||
void GetForceMutePolicy(HLERequestContext& ctx);
|
||||
void GetOutputModeSetting(HLERequestContext& ctx);
|
||||
void SetOutputModeSetting(HLERequestContext& ctx);
|
||||
void SetHeadphoneOutputLevelMode(HLERequestContext& ctx);
|
||||
void GetHeadphoneOutputLevelMode(HLERequestContext& ctx);
|
||||
void SetSpeakerAutoMuteEnabled(HLERequestContext& ctx);
|
||||
void IsSpeakerAutoMuteEnabled(HLERequestContext& ctx);
|
||||
void AcquireTargetNotification(HLERequestContext& ctx);
|
||||
|
||||
std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
|
||||
};
|
||||
|
||||
} // namespace Service::Audio
|
@ -2,9 +2,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/audio/audctl.h"
|
||||
#include "core/hle/service/audio/audin_u.h"
|
||||
#include "core/hle/service/audio/audio.h"
|
||||
#include "core/hle/service/audio/audio_controller.h"
|
||||
#include "core/hle/service/audio/audout_u.h"
|
||||
#include "core/hle/service/audio/audrec_a.h"
|
||||
#include "core/hle/service/audio/audrec_u.h"
|
||||
@ -18,7 +18,7 @@ namespace Service::Audio {
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
server_manager->RegisterNamedService("audctl", std::make_shared<IAudioController>(system));
|
||||
server_manager->RegisterNamedService("audctl", std::make_shared<AudCtl>(system));
|
||||
server_manager->RegisterNamedService("audout:u", std::make_shared<AudOutU>(system));
|
||||
server_manager->RegisterNamedService("audin:u", std::make_shared<AudInU>(system));
|
||||
server_manager->RegisterNamedService("audrec:a", std::make_shared<AudRecA>(system));
|
||||
|
@ -1,174 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/audio/audio_controller.h"
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/set/system_settings_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
IAudioController::IAudioController(Core::System& system_)
|
||||
: ServiceFramework{system_, "audctl"}, service_context{system, "audctl"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetTargetVolume"},
|
||||
{1, nullptr, "SetTargetVolume"},
|
||||
{2, C<&IAudioController::GetTargetVolumeMin>, "GetTargetVolumeMin"},
|
||||
{3, C<&IAudioController::GetTargetVolumeMax>, "GetTargetVolumeMax"},
|
||||
{4, nullptr, "IsTargetMute"},
|
||||
{5, nullptr, "SetTargetMute"},
|
||||
{6, nullptr, "IsTargetConnected"},
|
||||
{7, nullptr, "SetDefaultTarget"},
|
||||
{8, nullptr, "GetDefaultTarget"},
|
||||
{9, C<&IAudioController::GetAudioOutputMode>, "GetAudioOutputMode"},
|
||||
{10, C<&IAudioController::SetAudioOutputMode>, "SetAudioOutputMode"},
|
||||
{11, nullptr, "SetForceMutePolicy"},
|
||||
{12, C<&IAudioController::GetForceMutePolicy>, "GetForceMutePolicy"},
|
||||
{13, C<&IAudioController::GetOutputModeSetting>, "GetOutputModeSetting"},
|
||||
{14, C<&IAudioController::SetOutputModeSetting>, "SetOutputModeSetting"},
|
||||
{15, nullptr, "SetOutputTarget"},
|
||||
{16, nullptr, "SetInputTargetForceEnabled"},
|
||||
{17, C<&IAudioController::SetHeadphoneOutputLevelMode>, "SetHeadphoneOutputLevelMode"},
|
||||
{18, C<&IAudioController::GetHeadphoneOutputLevelMode>, "GetHeadphoneOutputLevelMode"},
|
||||
{19, nullptr, "AcquireAudioVolumeUpdateEventForPlayReport"},
|
||||
{20, nullptr, "AcquireAudioOutputDeviceUpdateEventForPlayReport"},
|
||||
{21, nullptr, "GetAudioOutputTargetForPlayReport"},
|
||||
{22, nullptr, "NotifyHeadphoneVolumeWarningDisplayedEvent"},
|
||||
{23, nullptr, "SetSystemOutputMasterVolume"},
|
||||
{24, nullptr, "GetSystemOutputMasterVolume"},
|
||||
{25, nullptr, "GetAudioVolumeDataForPlayReport"},
|
||||
{26, nullptr, "UpdateHeadphoneSettings"},
|
||||
{27, nullptr, "SetVolumeMappingTableForDev"},
|
||||
{28, nullptr, "GetAudioOutputChannelCountForPlayReport"},
|
||||
{29, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"},
|
||||
{30, C<&IAudioController::SetSpeakerAutoMuteEnabled>, "SetSpeakerAutoMuteEnabled"},
|
||||
{31, C<&IAudioController::IsSpeakerAutoMuteEnabled>, "IsSpeakerAutoMuteEnabled"},
|
||||
{32, nullptr, "GetActiveOutputTarget"},
|
||||
{33, nullptr, "GetTargetDeviceInfo"},
|
||||
{34, C<&IAudioController::AcquireTargetNotification>, "AcquireTargetNotification"},
|
||||
{35, nullptr, "SetHearingProtectionSafeguardTimerRemainingTimeForDebug"},
|
||||
{36, nullptr, "GetHearingProtectionSafeguardTimerRemainingTimeForDebug"},
|
||||
{37, nullptr, "SetHearingProtectionSafeguardEnabled"},
|
||||
{38, nullptr, "IsHearingProtectionSafeguardEnabled"},
|
||||
{39, nullptr, "IsHearingProtectionSafeguardMonitoringOutputForDebug"},
|
||||
{40, nullptr, "GetSystemInformationForDebug"},
|
||||
{41, nullptr, "SetVolumeButtonLongPressTime"},
|
||||
{42, nullptr, "SetNativeVolumeForDebug"},
|
||||
{10000, nullptr, "NotifyAudioOutputTargetForPlayReport"},
|
||||
{10001, nullptr, "NotifyAudioOutputChannelCountForPlayReport"},
|
||||
{10002, nullptr, "NotifyUnsupportedUsbOutputDeviceAttachedForPlayReport"},
|
||||
{10100, nullptr, "GetAudioVolumeDataForPlayReport"},
|
||||
{10101, nullptr, "BindAudioVolumeUpdateEventForPlayReport"},
|
||||
{10102, nullptr, "BindAudioOutputTargetUpdateEventForPlayReport"},
|
||||
{10103, nullptr, "GetAudioOutputTargetForPlayReport"},
|
||||
{10104, nullptr, "GetAudioOutputChannelCountForPlayReport"},
|
||||
{10105, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"},
|
||||
{10106, nullptr, "GetDefaultAudioOutputTargetForPlayReport"},
|
||||
{50000, nullptr, "SetAnalogInputBoostGainForPrototyping"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
|
||||
m_set_sys =
|
||||
system.ServiceManager().GetService<Service::Set::ISystemSettingsServer>("set:sys", true);
|
||||
notification_event = service_context.CreateEvent("IAudioController:NotificationEvent");
|
||||
}
|
||||
|
||||
IAudioController::~IAudioController() {
|
||||
service_context.CloseEvent(notification_event);
|
||||
};
|
||||
|
||||
Result IAudioController::GetTargetVolumeMin(Out<s32> out_target_min_volume) {
|
||||
LOG_DEBUG(Audio, "called.");
|
||||
|
||||
// This service function is currently hardcoded on the
|
||||
// actual console to this value (as of 8.0.0).
|
||||
*out_target_min_volume = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IAudioController::GetTargetVolumeMax(Out<s32> out_target_max_volume) {
|
||||
LOG_DEBUG(Audio, "called.");
|
||||
|
||||
// This service function is currently hardcoded on the
|
||||
// actual console to this value (as of 8.0.0).
|
||||
*out_target_max_volume = 15;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IAudioController::GetAudioOutputMode(Out<Set::AudioOutputMode> out_output_mode,
|
||||
Set::AudioOutputModeTarget target) {
|
||||
const auto result = m_set_sys->GetAudioOutputMode(out_output_mode, target);
|
||||
|
||||
LOG_INFO(Service_SET, "called, target={}, output_mode={}", target, *out_output_mode);
|
||||
R_RETURN(result);
|
||||
}
|
||||
|
||||
Result IAudioController::SetAudioOutputMode(Set::AudioOutputModeTarget target,
|
||||
Set::AudioOutputMode output_mode) {
|
||||
LOG_INFO(Service_SET, "called, target={}, output_mode={}", target, output_mode);
|
||||
|
||||
R_RETURN(m_set_sys->SetAudioOutputMode(target, output_mode));
|
||||
}
|
||||
|
||||
Result IAudioController::GetForceMutePolicy(Out<ForceMutePolicy> out_mute_policy) {
|
||||
LOG_WARNING(Audio, "(STUBBED) called");
|
||||
|
||||
// Removed on FW 13.2.1+
|
||||
*out_mute_policy = ForceMutePolicy::Disable;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IAudioController::GetOutputModeSetting(Out<Set::AudioOutputMode> out_output_mode,
|
||||
Set::AudioOutputModeTarget target) {
|
||||
LOG_WARNING(Audio, "(STUBBED) called, target={}", target);
|
||||
|
||||
*out_output_mode = Set::AudioOutputMode::ch_7_1;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IAudioController::SetOutputModeSetting(Set::AudioOutputModeTarget target,
|
||||
Set::AudioOutputMode output_mode) {
|
||||
LOG_INFO(Service_SET, "called, target={}, output_mode={}", target, output_mode);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IAudioController::SetHeadphoneOutputLevelMode(HeadphoneOutputLevelMode output_level_mode) {
|
||||
LOG_WARNING(Audio, "(STUBBED) called");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IAudioController::GetHeadphoneOutputLevelMode(
|
||||
Out<HeadphoneOutputLevelMode> out_output_level_mode) {
|
||||
LOG_INFO(Audio, "called");
|
||||
|
||||
*out_output_level_mode = HeadphoneOutputLevelMode::Normal;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IAudioController::SetSpeakerAutoMuteEnabled(bool is_speaker_auto_mute_enabled) {
|
||||
LOG_INFO(Audio, "called, is_speaker_auto_mute_enabled={}", is_speaker_auto_mute_enabled);
|
||||
|
||||
R_RETURN(m_set_sys->SetSpeakerAutoMuteFlag(is_speaker_auto_mute_enabled));
|
||||
}
|
||||
|
||||
Result IAudioController::IsSpeakerAutoMuteEnabled(Out<bool> out_is_speaker_auto_mute_enabled) {
|
||||
const auto result = m_set_sys->GetSpeakerAutoMuteFlag(out_is_speaker_auto_mute_enabled);
|
||||
|
||||
LOG_INFO(Audio, "called, is_speaker_auto_mute_enabled={}", *out_is_speaker_auto_mute_enabled);
|
||||
R_RETURN(result);
|
||||
}
|
||||
|
||||
Result IAudioController::AcquireTargetNotification(
|
||||
OutCopyHandle<Kernel::KReadableEvent> out_notification_event) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
*out_notification_event = ¬ification_event->GetReadableEvent();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace Service::Audio
|
@ -1,58 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/set/settings_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::Set {
|
||||
class ISystemSettingsServer;
|
||||
}
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
class IAudioController final : public ServiceFramework<IAudioController> {
|
||||
public:
|
||||
explicit IAudioController(Core::System& system_);
|
||||
~IAudioController() override;
|
||||
|
||||
private:
|
||||
enum class ForceMutePolicy {
|
||||
Disable,
|
||||
SpeakerMuteOnHeadphoneUnplugged,
|
||||
};
|
||||
|
||||
enum class HeadphoneOutputLevelMode {
|
||||
Normal,
|
||||
HighPower,
|
||||
};
|
||||
|
||||
Result GetTargetVolumeMin(Out<s32> out_target_min_volume);
|
||||
Result GetTargetVolumeMax(Out<s32> out_target_max_volume);
|
||||
Result GetAudioOutputMode(Out<Set::AudioOutputMode> out_output_mode,
|
||||
Set::AudioOutputModeTarget target);
|
||||
Result SetAudioOutputMode(Set::AudioOutputModeTarget target, Set::AudioOutputMode output_mode);
|
||||
Result GetForceMutePolicy(Out<ForceMutePolicy> out_mute_policy);
|
||||
Result GetOutputModeSetting(Out<Set::AudioOutputMode> out_output_mode,
|
||||
Set::AudioOutputModeTarget target);
|
||||
Result SetOutputModeSetting(Set::AudioOutputModeTarget target,
|
||||
Set::AudioOutputMode output_mode);
|
||||
Result SetHeadphoneOutputLevelMode(HeadphoneOutputLevelMode output_level_mode);
|
||||
Result GetHeadphoneOutputLevelMode(Out<HeadphoneOutputLevelMode> out_output_level_mode);
|
||||
Result SetSpeakerAutoMuteEnabled(bool is_speaker_auto_mute_enabled);
|
||||
Result IsSpeakerAutoMuteEnabled(Out<bool> out_is_speaker_auto_mute_enabled);
|
||||
Result AcquireTargetNotification(OutCopyHandle<Kernel::KReadableEvent> out_notification_event);
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
Kernel::KEvent* notification_event;
|
||||
std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
|
||||
};
|
||||
|
||||
} // namespace Service::Audio
|
@ -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
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user