Represent paddle bindings as a tagged value, not a flat enum

Keyboard and extended mouse bindings can't be enumerated the way gamepad
buttons can — a key binding is a value drawn from ~256 virtual-key codes,
not a member of a closed set. BackButtonConfig held four bare
BackButtonAction values, so there was nowhere to put one.

Introduce BackButtonBinding, a kind/code pair covering built-in actions,
keyboard VK codes, and extended mouse buttons. No behaviour change: only
Kind::Action is produced or consumed today. This is the groundwork for
capturing keys and mouse buttons in the remap window.

Both formats extend without a migration:

- Registry: packed as (kind << 16) | code. Every value written by earlier
  builds is a bare BackButtonAction of 0-18, so its kind bits are already
  zero and it decodes as Kind::Action. Action bindings still pack to their
  bare value, so a downgrade also reads settings written by this build.
- WebView2: built-in actions keep their bare ids ("A", "leftMouse"), and
  the open-ended kinds are prefixed ("key:32", "mouse:x1"). Flat strings
  keep the hand-rolled JSON reader in RemapWindow usable, and the JS input
  catalog needs no changes.

Unrecognised values — corrupt, or written by a future build that knows
kinds this one does not — degrade to None rather than being applied.

Verified the invariants with a standalone test over the full value space:
legacy decoding, pack and id round-trips for every kind, rejection of
malformed input, and the shipped defaults.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dylan DeverillandClaude Opus 5 committed 2026-08-05 18:55:24 -07:00
1 parent ba215b2429
commit 0839f79f16
6 files changed
+166 -49

No files matched your search

+8 -5
View File
@@ -116,6 +116,8 @@ struct ControllerManager::Slot {
static bool DetectCapture(const uint8_t* cur, const uint8_t* prev,
BackButtonAction& out)
{
// Controller input only ever yields a built-in action; the caller wraps the
// result. Keyboard and mouse capture arrive through the UI, not this loop.
using BA = BackButtonAction;
// Helper: rising edge on a single bit in a byte.
@@ -304,7 +306,7 @@ void ControllerManager::SetControllerPlatform(ControllerPlatform platform) {
}
}
void ControllerManager::StartButtonCapture(std::function<void(BackButtonAction)> callback) {
void ControllerManager::StartButtonCapture(std::function<void(const BackButtonBinding&)> callback) {
std::lock_guard<std::mutex> lk(m_captureMutex);
m_captureCallback = std::move(callback);
m_capturing = true;
@@ -779,7 +781,7 @@ void ControllerManager::ReadLoop(Slot* slot) {
// Fire mouse button events for back paddles mapped to LeftMouseButton/RightMouseButton.
// Uses edge detection so we only send DOWN on press and UP on release.
if (hasPrev) {
struct PaddleEdge { bool cur; bool prev; BackButtonAction action; };
struct PaddleEdge { bool cur; bool prev; const BackButtonBinding& binding; };
const PaddleEdge edges[] = {
{ n>4 && (buf[4]&SteamController::BTN_L4)!=0, (prevBuf[4]&SteamController::BTN_L4)!=0, m_backConfig.l4 },
{ n>4 && (buf[4]&SteamController::BTN_L5)!=0, (prevBuf[4]&SteamController::BTN_L5)!=0, m_backConfig.l5 },
@@ -789,10 +791,10 @@ void ControllerManager::ReadLoop(Slot* slot) {
for (const auto& e : edges) {
if (e.cur == e.prev) continue;
DWORD flag = 0;
if (e.action == BackButtonAction::LeftMouseButton) {
if (e.binding.IsAction(BackButtonAction::LeftMouseButton)) {
flag = e.cur ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
slot->paddleMouseLeftDown = e.cur;
} else if (e.action == BackButtonAction::RightMouseButton) {
} else if (e.binding.IsAction(BackButtonAction::RightMouseButton)) {
flag = e.cur ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
slot->paddleMouseRightDown = e.cur;
}
@@ -807,7 +809,8 @@ void ControllerManager::ReadLoop(Slot* slot) {
if (DetectCapture(buf, prevBuf, captured)) {
m_capturing = false;
std::lock_guard<std::mutex> lk(m_captureMutex);
if (m_captureCallback) m_captureCallback(captured);
if (m_captureCallback)
m_captureCallback(BackButtonBinding::FromAction(captured));
}
}