From de07d48d6e074cdd79db71937a366107740e57d0 Mon Sep 17 00:00:00 2001 From: Tim Shen Date: Thu, 22 Oct 2026 15:18:02 +0900 Subject: [PATCH] feat(input): add setting to turn off Guide button shortcuts Guide is deferred until release so Nova can claim Guide+Start and Guide+Y, which means a lone Guide press reaches the host only as a synthesized tap on release and a held Guide is never seen by the host. Add a "Guide button shortcuts" checkbox (default on, so behavior is unchanged). When off, Guide is sent to the host on press and release like any other button. Co-Authored-By: Claude Opus 5.5 --- .../nova/binding/input/ControllerHandler.kt | 4 ++- .../input/NovaControllerShortcutState.kt | 4 +++ .../preferences/PreferenceConfiguration.kt | 4 +++ app/src/main/res/values/strings.xml | 2 ++ app/src/main/res/xml/preferences.xml | 6 +++++ .../input/NovaControllerShortcutStateTest.kt | 26 +++++++++++++++++++ 6 files changed, 45 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/papi/nova/binding/input/ControllerHandler.kt b/app/src/main/java/com/papi/nova/binding/input/ControllerHandler.kt index 579e347bf..c99a707c5 100644 --- a/app/src/main/java/com/papi/nova/binding/input/ControllerHandler.kt +++ b/app/src/main/java/com/papi/nova/binding/input/ControllerHandler.kt @@ -2674,7 +2674,9 @@ class ControllerHandler( var startDownTime = 0L var startUpTime = 0L var backMenuPending = false - val novaShortcutState = NovaControllerShortcutState() + val novaShortcutState = NovaControllerShortcutState().apply { + guideShortcutsEnabled = prefConfig.guideShortcuts + } val batteryStateUpdateRunnable: Runnable = object : Runnable { override fun run() { diff --git a/app/src/main/java/com/papi/nova/binding/input/NovaControllerShortcutState.kt b/app/src/main/java/com/papi/nova/binding/input/NovaControllerShortcutState.kt index 01c313ff7..c971b808c 100644 --- a/app/src/main/java/com/papi/nova/binding/input/NovaControllerShortcutState.kt +++ b/app/src/main/java/com/papi/nova/binding/input/NovaControllerShortcutState.kt @@ -25,6 +25,7 @@ class NovaControllerShortcutState { private var yDown = false private var backYConsumedByNova = false var loneAppSwitchOpensQuickMenu = false + var guideShortcutsEnabled = true private var appSwitchConsumedByNova = false fun onButtonDown(keyCode: Int, repeatCount: Int): NovaControllerShortcutAction { @@ -61,6 +62,9 @@ class NovaControllerShortcutState { } if (keyCode == KeyEvent.KEYCODE_BUTTON_MODE) { + if (!guideShortcutsEnabled) { + return NovaControllerShortcutAction.NONE + } if (repeatCount == 0) { guideDown = true guideForwardedToHost = false diff --git a/app/src/main/java/com/papi/nova/preferences/PreferenceConfiguration.kt b/app/src/main/java/com/papi/nova/preferences/PreferenceConfiguration.kt index 10bb82636..3577eb74e 100644 --- a/app/src/main/java/com/papi/nova/preferences/PreferenceConfiguration.kt +++ b/app/src/main/java/com/papi/nova/preferences/PreferenceConfiguration.kt @@ -78,6 +78,7 @@ class PreferenceConfiguration { @JvmField var multiController = false @JvmField var usbDriver = false @JvmField var flipFaceButtons = false + @JvmField var guideShortcuts = true @JvmField var onscreenController = false @JvmField var onscreenControllerLayoutPreset: String? = null @JvmField var hideOSCWhenHasGamepad = false @@ -236,6 +237,7 @@ class PreferenceConfiguration { private const val VIBRATE_FALLBACK_PREF_STRING = "checkbox_vibrate_fallback" private const val VIBRATE_FALLBACK_STRENGTH_PREF_STRING = "seekbar_vibrate_fallback_strength" private const val FLIP_FACE_BUTTONS_PREF_STRING = "checkbox_flip_face_buttons" + private const val GUIDE_SHORTCUTS_PREF_STRING = "checkbox_guide_shortcuts" private const val LATENCY_TOAST_PREF_STRING = "checkbox_enable_post_stream_toast" private const val FRAME_PACING_PREF_STRING = "frame_pacing" private const val LOW_LATENCY_FRAME_BALANCE_PREF_STRING = "pref_low_latency_frame_balance" @@ -335,6 +337,7 @@ class PreferenceConfiguration { private const val DEFAULT_VIBRATE_FALLBACK = false private const val DEFAULT_VIBRATE_FALLBACK_STRENGTH = 100 private const val DEFAULT_FLIP_FACE_BUTTONS = false + private const val DEFAULT_GUIDE_SHORTCUTS = true private const val DEFAULT_AUDIO_CONFIG = "2" private const val DEFAULT_LATENCY_TOAST = false private const val DEFAULT_FRAME_PACING = "latency" @@ -933,6 +936,7 @@ class PreferenceConfiguration { config.vibrateFallbackToDeviceStrength = prefs.getInt(VIBRATE_FALLBACK_STRENGTH_PREF_STRING, DEFAULT_VIBRATE_FALLBACK_STRENGTH) config.flipFaceButtons = prefs.getBoolean(FLIP_FACE_BUTTONS_PREF_STRING, DEFAULT_FLIP_FACE_BUTTONS) + config.guideShortcuts = prefs.getBoolean(GUIDE_SHORTCUTS_PREF_STRING, DEFAULT_GUIDE_SHORTCUTS) config.enableLatencyToast = prefs.getBoolean(LATENCY_TOAST_PREF_STRING, DEFAULT_LATENCY_TOAST) config.enableBackMenu = prefs.getBoolean(CHECKBOX_ENABLE_QUIT_DIALOG, true) config.enableFloatingButton = diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index eb30b80b2..5dd61801e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -892,6 +892,8 @@ Remember mouse mode selected during stream. When disabled, mouse mode changed during stream will be temporary. Flip face buttons Switches the face buttons A/B and X/Y for gamepads and the on-screen controls + Guide button shortcuts + Use Guide+Start for the quick menu and Guide+Y for the HUD. Turn off to send Guide presses and holds to the host immediately. Always control mouse with touchpad Forces gamepad touchpad input to control the host mouse, even when emulating a gamepad with a touchpad. Allow use of gamepad motion sensors diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index d53ced960..a55740788 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -338,6 +338,12 @@ android:summary="@string/summary_checkbox_flip_face_buttons" android:title="@string/title_checkbox_flip_face_buttons" app:iconSpaceReserved="false" /> +