fix(input): recover cleanly on joystick reconnect

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Andrew Quenehen
2026-08-01 03:53:43 -03:00
parent da60f40dfc
commit 7504753ea8
3 changed files with 76 additions and 1 deletions
+12
View File
@@ -393,6 +393,13 @@ function love.joystickhat(joystick, hat, direction)
Game:joystickhat(joystick, hat, direction) Game:joystickhat(joystick, hat, direction)
end end
function love.joystickadded(joystick)
SwitchDiagnostics.onJoystickEvent("joystickadded", joystick)
if editorMode or TouchEditor then return end
if Importer then return end
Game:joystickadded(joystick)
end
function love.joystickremoved(joystick) function love.joystickremoved(joystick)
SwitchDiagnostics.onJoystickEvent("joystickremoved", joystick) SwitchDiagnostics.onJoystickEvent("joystickremoved", joystick)
if editorMode or TouchEditor then return end if editorMode or TouchEditor then return end
@@ -423,6 +430,11 @@ function love.visible(v)
Game:visible(v) Game:visible(v)
end end
function love.lowmemory()
if editorMode or TouchEditor or Importer then return end
if Game then Game:onResume() end
end
function love.touchpressed(id, x, y, dx, dy, pressure) function love.touchpressed(id, x, y, dx, dy, pressure)
if editorMode then return end if editorMode then return end
if TouchEditor then if TouchEditor then
+34 -1
View File
@@ -546,15 +546,48 @@ function Game:focus(f)
end end
function Game:visible(v) function Game:visible(v)
if v then
self:onResume()
else
Input:reset()
TouchControls:reset()
end
end
function Game:onResume()
Input:reset() Input:reset()
TouchControls:reset() TouchControls:reset()
-- Chip music may survive suspend as a duplicate stream; stop it and let
-- the active screen re-cue on the next frame (hardware audio check: T19).
require("src.core.ChipAudio").stopMusic()
local SwitchDiagnostics = require("src.debug.SwitchDiagnostics")
if SwitchDiagnostics.isEnabled() then
SwitchDiagnostics.onEvent("lifecycle", { event = "resume" })
end
end
function Game:recoverInput(event, joystick)
Input:reset()
TouchControls:reset()
local SwitchDiagnostics = require("src.debug.SwitchDiagnostics")
if SwitchDiagnostics.isEnabled() then
if joystick then
SwitchDiagnostics.onJoystickEvent(event, joystick)
else
SwitchDiagnostics.onEvent("lifecycle", { event = event })
end
end
end
function Game:joystickadded(joystick)
self:recoverInput("joystickadded", joystick)
end end
-- A disconnected/dropped controller can't send the button-up for whatever -- A disconnected/dropped controller can't send the button-up for whatever
-- it was holding, so drop all input state rather than try to guess which -- it was holding, so drop all input state rather than try to guess which
-- flags it owned. -- flags it owned.
function Game:joystickremoved(joystick) function Game:joystickremoved(joystick)
Input:reset() self:recoverInput("joystickremoved", joystick)
TouchControls:joystickremoved() TouchControls:joystickremoved()
end end
@@ -0,0 +1,30 @@
-- Joystick remove / resume clears stuck input sources (SWNX-19).
package.path = "./?.lua;./?/init.lua;" .. package.path
if not _G.love then _G.love = require("tests.love_stub") end
local T = require("tests.harness")
local check = T.check
local Input = require("src.core.Input")
local Game = require("src.core.Game")
Input:init()
Input:gamepadaxis(nil, "leftx", -0.9)
Input:step()
check(Input:isDown("left"), "stick holds left before disconnect")
Game:joystickremoved({})
check(not Input:isDown("left"), "joystickremoved clears stick hold")
Input:gamepadpressed(nil, "a")
Input:step()
check(Input:isDown("a"), "button held before reconnect reset")
Game:joystickadded({ getName = function() return "Joy-Con" end })
check(not Input:isDown("a"), "joystickadded clears stale button hold")
Input:gamepadaxis(nil, "lefty", 0.9)
Input:step()
Game:onResume()
check(not Input:isDown("down"), "resume clears stuck axis state")
T.finish()