Quick answer: Refresh the controller handle each frame via GetConnectedControllers. Action set handles are stable; controller handles may change. Call RunFrame before activation.
A game switches from Gameplay to UI action set on menu open. SteamInput::ActivateActionSet returns without error but bindings don’t change. Controller handle was stale.
Refresh Controller Handle
InputHandle_t handles[STEAM_INPUT_MAX_COUNT];
int count = SteamInput()->GetConnectedControllers(handles);
for (int i = 0; i < count; i++) {
SteamInput()->ActivateActionSet(handles[i], uiActionSetHandle);
}
Don’t cache handles across frames. Refresh per call.
Get Action Set Handles Once
InputActionSetHandle_t uiActionSet = SteamInput()->GetActionSetHandle("UIControls");
InputActionSetHandle_t gameplayActionSet = SteamInput()->GetActionSetHandle("GameplayControls");
Action set handles are stable; obtain once at startup.
RunFrame Before Activation
SteamInput()->RunFrame(); // pump events
// now safe to ActivateActionSet, GetDigitalActionData, etc.
Call RunFrame every game frame before reading or writing Steam Input.
Verify ActionSet in Steam Overlay
While playing, Shift+Tab → Steam overlay → Controller Configuration. Live binding display shows current action set. Confirms whether your code switched correctly.
Verifying
Open menu; UI bindings active. Close menu; gameplay bindings active. No stale frames where wrong action set is read.
“Refresh controller handles per frame; action sets are stable. RunFrame ties it together.”
For Steam Deck, the Deck’s back paddles map per action set — design two sets so users can configure paddles differently in menus vs gameplay.