diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index f4ddde2e25..40c0af0b24 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -18,6 +18,7 @@ import android.content.IntentFilter import android.content.res.Configuration import android.graphics.Rect import android.graphics.drawable.Icon +import android.hardware.input.InputManager import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener @@ -63,11 +64,12 @@ import kotlin.math.roundToInt import org.yuzu.yuzu_emu.utils.ForegroundService import androidx.core.os.BundleCompat -class EmulationActivity : AppCompatActivity(), SensorEventListener { +class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager.InputDeviceListener { private lateinit var binding: ActivityEmulationBinding var isActivityRecreated = false private lateinit var nfcReader: NfcReader + private lateinit var inputManager: InputManager private var touchDownTime: Long = 0 private val maxTapDuration = 500L @@ -140,6 +142,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { nfcReader = NfcReader(this) nfcReader.initialize() + inputManager = getSystemService(INPUT_SERVICE) as InputManager + inputManager.registerInputDeviceListener(this, null) + foregroundService = Intent(this, ForegroundService::class.java) startForegroundService(foregroundService) @@ -206,9 +211,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { override fun onDestroy() { super.onDestroy() + inputManager.unregisterInputDeviceListener(this) stopForegroundService(this) NativeLibrary.playTimeManagerStop() - } override fun onUserLeaveHint() { @@ -244,8 +249,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { val isPhysicalKeyboard = event.source and InputDevice.SOURCE_KEYBOARD == InputDevice.SOURCE_KEYBOARD && event.device?.isVirtual == false - if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK && - event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD && + val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK || + event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD + + if (!isControllerInput && event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE && !isPhysicalKeyboard ) { @@ -256,12 +263,18 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { return super.dispatchKeyEvent(event) } + if (isControllerInput && event.action == KeyEvent.ACTION_DOWN) { + notifyControllerInput() + } + return InputHandler.dispatchKeyEvent(event) } override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { - if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK && - event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD && + val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK || + event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD + + if (!isControllerInput && event.source and InputDevice.SOURCE_KEYBOARD != InputDevice.SOURCE_KEYBOARD && event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE ) { @@ -277,9 +290,54 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { return true } + if (isControllerInput) { + notifyControllerInput() + } + return InputHandler.dispatchGenericMotionEvent(event) } + private fun notifyControllerInput() { + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment + val emulationFragment = + navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment + emulationFragment?.onControllerInputDetected() + } + + private fun isGameController(deviceId: Int): Boolean { + val device = InputDevice.getDevice(deviceId) ?: return false + val sources = device.sources + return sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD || + sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK + } + + override fun onInputDeviceAdded(deviceId: Int) { + if (isGameController(deviceId)) { + InputHandler.updateControllerData() + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment + val emulationFragment = + navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment + emulationFragment?.onControllerConnected() + } + } + + override fun onInputDeviceRemoved(deviceId: Int) { + InputHandler.updateControllerData() + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment + val emulationFragment = + navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment + emulationFragment?.onControllerDisconnected() + } + + override fun onInputDeviceChanged(deviceId: Int) { + if (isGameController(deviceId)) { + InputHandler.updateControllerData() + } + } + override fun onSensorChanged(event: SensorEvent) { val rotation = this.display?.rotation if (rotation == Surface.ROTATION_90) { @@ -519,8 +577,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { when (event.action) { MotionEvent.ACTION_DOWN -> { touchDownTime = System.currentTimeMillis() - // show overlay immediately on touch and cancel timer - if (!emulationViewModel.drawerOpen.value) { + // show overlay immediately on touch and cancel timer when only auto-hide is enabled + if (!emulationViewModel.drawerOpen.value && + BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean() && + !BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) { fragment.handler.removeCallbacksAndMessages(null) fragment.showOverlay() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 2e72e15846..475d9192c6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -52,6 +52,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { SOC_OVERLAY_BACKGROUND("soc_overlay_background"), ENABLE_INPUT_OVERLAY_AUTO_HIDE("enable_input_overlay_auto_hide"), + HIDE_OVERLAY_ON_CONTROLLER_INPUT("hide_overlay_on_controller_input"), PERF_OVERLAY_BACKGROUND("perf_overlay_background"), SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index b1fe56a866..62929bf371 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -387,6 +387,13 @@ abstract class SettingsItem( valueHint = R.string.seconds ) ) + put( + SwitchSetting( + BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT, + titleId = R.string.hide_overlay_on_controller_input, + descriptionId = R.string.hide_overlay_on_controller_input_description + ) + ) put( SwitchSetting( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 80b6ddb7b2..8d05abf703 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -274,6 +274,7 @@ class SettingsFragmentPresenter( sl.apply { add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key) add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key) + add(BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.key) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 37e187380e..3f5abc0858 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -93,7 +93,6 @@ import org.yuzu.yuzu_emu.utils.collect import org.yuzu.yuzu_emu.utils.CustomSettingsHandler import java.io.ByteArrayOutputStream import java.io.File -import kotlin.coroutines.coroutineContext import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -106,6 +105,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { val handler = Handler(Looper.getMainLooper()) private var isOverlayVisible = true + private var controllerInputReceived = false private var _binding: FragmentEmulationBinding? = null @@ -656,6 +656,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(newState) updateQuickOverlayMenuEntry(newState) binding.surfaceInputOverlay.refreshControls() + // Sync view visibility with the setting + if (newState) { + showOverlay() + } else { + hideOverlay() + } NativeConfig.saveGlobalConfig() true } @@ -1901,7 +1907,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { companion object { fun fromValue(value: Int): AmiiboState = - values().firstOrNull { it.value == value } ?: Disabled + entries.firstOrNull { it.value == value } ?: Disabled } } @@ -1914,7 +1920,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { companion object { fun fromValue(value: Int): AmiiboLoadResult = - values().firstOrNull { it.value == value } ?: Unknown + entries.firstOrNull { it.value == value } ?: Unknown } } @@ -1971,6 +1977,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { fun showOverlay() { if (!isOverlayVisible) { isOverlayVisible = true + // Reset controller input flag so controller can hide overlay again + controllerInputReceived = false ViewUtils.showView(binding.surfaceInputOverlay, 500) } } @@ -1978,7 +1986,26 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { private fun hideOverlay() { if (isOverlayVisible) { isOverlayVisible = false - ViewUtils.hideView(binding.surfaceInputOverlay, 500) + ViewUtils.hideView(binding.surfaceInputOverlay) } } + + fun onControllerInputDetected() { + if (!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) return + if (!BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) return + if (controllerInputReceived) return + controllerInputReceived = true + hideOverlay() + } + + fun onControllerConnected() { + controllerInputReceived = false + } + + fun onControllerDisconnected() { + if (!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) return + if (!BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) return + controllerInputReceived = false + showOverlay() + } } diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h index 19ac95652b..e276f19284 100644 --- a/src/android/app/src/main/jni/android_settings.h +++ b/src/android/app/src/main/jni/android_settings.h @@ -92,6 +92,11 @@ namespace AndroidSettings { Settings::Setting input_overlay_auto_hide{linkage, 5, "input_overlay_auto_hide", Settings::Category::Overlay, Settings::Specialization::Default, true, true, &enable_input_overlay_auto_hide}; + Settings::Setting hide_overlay_on_controller_input{linkage, false, + "hide_overlay_on_controller_input", + Settings::Category::Overlay, + Settings::Specialization::Default, true, + true}; Settings::Setting perf_overlay_background{linkage, false, "perf_overlay_background", Settings::Category::Overlay, Settings::Specialization::Default, true, diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index ef8082a849..5c4854454a 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -26,6 +26,8 @@ Overlay Auto Hide Automatically hide the touch controls overlay after the specified time of inactivity. Enable Overlay Auto Hide + Hide Overlay on Controller Input + Automatically hide the touch controls overlay when a physical controller is used. Overlay reappears when controller is disconnected. Input Overlay Configure on-screen controls