Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/src/main/java/app/gamenative/PrefManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,13 @@ object PrefManager {
setPref(SHOW_FPS, value)
}

private val PERFORMANCE_HUD_HOTKEY_ENABLED = booleanPreferencesKey("performance_hud_hotkey_enabled")
var performanceHudHotkeyEnabled: Boolean
get() = getPref(PERFORMANCE_HUD_HOTKEY_ENABLED, false)
set(value) {
setPref(PERFORMANCE_HUD_HOTKEY_ENABLED, value)
}

private val PERFORMANCE_HUD_COMPACT_MODE = booleanPreferencesKey("performance_hud_compact_mode")
var performanceHudCompactMode: Boolean
get() = getPref(PERFORMANCE_HUD_COMPACT_MODE, false)
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/java/app/gamenative/ui/component/QuickMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ fun QuickMenu(
performanceHudConfig: PerformanceHudConfig = PerformanceHudConfig(),
onPerformanceHudConfigChanged: (PerformanceHudConfig) -> Unit = {},
hasPhysicalController: Boolean = false,
isPerformanceHudHotkeyEnabled: Boolean = false,
onPerformanceHudHotkeyToggled: () -> Unit = {},
activeToggleIds: Set<Int> = emptySet(),
modifier: Modifier = Modifier,
) {
Expand Down Expand Up @@ -462,6 +464,8 @@ fun QuickMenu(
onItemSelected(QuickMenuAction.PERFORMANCE_HUD)
},
onPerformanceHudConfigChanged = onPerformanceHudConfigChanged,
isHotkeyEnabled = isPerformanceHudHotkeyEnabled,
onHotkeyToggled = onPerformanceHudHotkeyToggled,
scrollState = hudScrollState,
focusRequester = hudItemFocusRequester,
modifier = Modifier.fillMaxSize(),
Expand Down Expand Up @@ -543,6 +547,8 @@ private fun PerformanceHudQuickMenuTab(
performanceHudConfig: PerformanceHudConfig,
onTogglePerformanceHud: () -> Unit,
onPerformanceHudConfigChanged: (PerformanceHudConfig) -> Unit,
isHotkeyEnabled: Boolean = false,
onHotkeyToggled: () -> Unit = {},
scrollState: ScrollState,
focusRequester: FocusRequester? = null,
modifier: Modifier = Modifier,
Expand All @@ -564,6 +570,14 @@ private fun PerformanceHudQuickMenuTab(
focusRequester = focusRequester,
)

QuickMenuToggleRow(
title = stringResource(R.string.performance_hud_hotkey_toggle),
subtitle = stringResource(R.string.performance_hud_hotkey_description),
enabled = isHotkeyEnabled,
onToggle = onHotkeyToggled,
accentColor = accentColor,
)

Spacer(modifier = Modifier.height(8.dp))

QuickMenuSectionHeader(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import java.util.TimerTask
class PhysicalControllerHandler(
private var profile: ControlsProfile?,
private val xServer: XServer?,
private val onOpenNavigationMenu: (() -> Unit)? = null
private val onOpenNavigationMenu: (() -> Unit)? = null,
private val onTogglePerformanceHud: (() -> Unit)? = null,
private val isPerformanceHudHotkeyEnabled: () -> Boolean = { false },
Comment on lines +25 to +27
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

The controller chord still leaks into normal game input.

Only the final ACTION_DOWN is consumed. The first three button-downs still fall through to profile/game handling, and after heldButtons.clear() all four ACTION_UPs do too. That means the hotkey can still trip Binding.OPEN_NAVIGATION_MENU or any mapped game action before the HUD toggles, and the last button can emit an unmatched release. Buffer/suppress the whole chord until it resolves, then either swallow or replay the sequence.

Also applies to: 36-46, 86-96

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/app/gamenative/ui/screen/xserver/PhysicalControllerHandler.kt`
around lines 25 - 27, The chord handling currently only consumes the final
ACTION_DOWN and clears heldButtons, letting the initial ACTION_DOWNs and later
ACTION_UPs fall through; modify the logic that processes controller input (the
code paths using heldButtons, ACTION_DOWN, ACTION_UP) so that all press/release
events for a potential chord are buffered/suppressed until the chord resolves
(use a temporary buffer/list to collect events instead of forwarding
immediately), then when the chord is recognized call onTogglePerformanceHud() or
onOpenNavigationMenu() and either swallow the buffered events or replay them in
correct order (replaying paired DOWN/UP to avoid unmatched releases) based on
whether the chord should block game input; ensure
isPerformanceHudHotkeyEnabled() gating remains and remove the immediate
heldButtons.clear() forwarding behavior so no initial downs fall through.

) {
private val TAG = "gncontrol"
private val mouseMoveOffset = PointF(0f, 0f)
Expand All @@ -31,6 +33,18 @@ class PhysicalControllerHandler(
// accessed only from main thread (MotionEvent dispatch + Compose lifecycle), no sync needed.
private val activeAxisBindings = mutableSetOf<Int>()

// Track currently-held gamepad buttons for combo detection
private val heldButtons = mutableSetOf<Int>()

companion object {
private val COMBO_KEYCODES = setOf(
KeyEvent.KEYCODE_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_SELECT,
KeyEvent.KEYCODE_BUTTON_L1,
KeyEvent.KEYCODE_BUTTON_R1,
)
}

private fun releaseActiveAxes() {
val controller = profile?.getController("*") ?: return
for (keyCode in activeAxisBindings) {
Expand Down Expand Up @@ -69,6 +83,18 @@ class PhysicalControllerHandler(
* Extracted from InputControlsView.onKeyEvent()
*/
fun onKeyEvent(event: KeyEvent): Boolean {
// Perf HUD hotkey combo detection
if (event.repeatCount == 0 && event.keyCode in COMBO_KEYCODES) {
if (event.action == KeyEvent.ACTION_DOWN) heldButtons.add(event.keyCode)
Comment thread
xXJSONDeruloXx marked this conversation as resolved.
else if (event.action == KeyEvent.ACTION_UP) heldButtons.remove(event.keyCode)

if (heldButtons.containsAll(COMBO_KEYCODES) && isPerformanceHudHotkeyEnabled()) {
heldButtons.clear()
onTogglePerformanceHud?.invoke()
return true // consume all four buttons
}
}

if (profile != null && event.repeatCount == 0) {
val controller = profile?.getController(event.deviceId)
if (controller != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1734,7 +1734,18 @@ fun XServerScreen(
Timber.d("=== Profile Loading Complete ===")
setProfile(targetProfile)

physicalControllerHandler = PhysicalControllerHandler(targetProfile, xServerView.getxServer(), gameBack)
physicalControllerHandler = PhysicalControllerHandler(
targetProfile,
xServerView.getxServer(),
gameBack,
onTogglePerformanceHud = {
val enabled = !isPerformanceHudEnabled
isPerformanceHudEnabled = enabled
PrefManager.showFps = enabled
updatePerformanceHud(enabled)
},
isPerformanceHudHotkeyEnabled = { PrefManager.performanceHudHotkeyEnabled },
)

// Store profile for auto-show logic
loadedProfile = targetProfile
Expand Down Expand Up @@ -2040,6 +2051,10 @@ fun XServerScreen(
performanceHudConfig = performanceHudConfig,
onPerformanceHudConfigChanged = ::applyPerformanceHudConfig,
hasPhysicalController = hasPhysicalController,
isPerformanceHudHotkeyEnabled = PrefManager.performanceHudHotkeyEnabled,
onPerformanceHudHotkeyToggled = {
PrefManager.performanceHudHotkeyEnabled = !PrefManager.performanceHudHotkeyEnabled
},
activeToggleIds = buildSet {
if (areControlsVisible) add(QuickMenuAction.INPUT_CONTROLS)
},
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@
<string name="quick_menu_tab_controller">Controller</string>
<string name="performance_hud">Performance HUD</string>
<string name="performance_hud_description">Show or hide the in-game overlay.</string>
<string name="performance_hud_hotkey_toggle">Controller Hotkey</string>
<string name="performance_hud_hotkey_description">Start + Select + L + R to toggle HUD.</string>
<string name="performance_hud_presets">Presets</string>
<string name="performance_hud_presets_description">Quickly switch between common HUD layouts.</string>
<string name="performance_hud_preset_fps_only">1</string>
Expand Down
Loading