Move the synthetic-input toggle to Controllers under Debug details

"Forward synthetic test input" is a controller-debug aid, so it now lives in the
Controllers panel (gated behind Debug details, disabled until the XInput hook is
attached) instead of the Injection panel. ControllersPanel owns the flag and exposes
test_input(); main feeds it into InjectionPanel::set_test_input each frame, so the
existing synthetic-pad substitution in publish() is unchanged.

Verified: x64 build green; review (default view no longer shows it in Injection;
appears in Controllers under Debug details).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 01:23:41 +02:00
parent 0ffe836a29
commit a32e78ffef
6 changed files with 34 additions and 18 deletions

View File

@@ -75,12 +75,6 @@ is removed from this list once done — so the top item is always next. The
self-verifiable tooling / UI / input items come first; the game-pipeline items that
need a real game (and Remote Play) to fully validate come last.
- **Move the synthetic-input toggle to the Controllers panel, under Debug details.**
The "Forward synthetic test input" checkbox is a controller-debugging aid, so move
it out of the Injection panel into `ControllersPanel` and gate it behind
`debug_details`. The publish path is unchanged (the Injection panel already
substitutes the synthetic pattern in `publish`); the flag just moves with it (or is
passed from Controllers into the publish call).
- **Mouse & keyboard forwarding (messages + polling-state hooks).** Forward guest
clicks and keystrokes into the unfocused game via a new **MKB hook subsystem** in
`coop_hook.dll` — the toggle *is* the hook (not installed → no forwarding).

View File

@@ -91,6 +91,21 @@ void ControllersPanel::draw(const InputSource& input, const HookStatusView& stat
}
#endif
// Synthetic test input (debug aid): drives the game with a non-human pattern so
// forwarding can be proven without a real controller. Only meaningful once the
// XInput hook is attached.
if (debug_details)
{
ImGui::BeginDisabled(!status.attached);
ImGui::Checkbox("Forward synthetic test input", &test_input_);
ImGui::EndDisabled();
if (test_input_)
{
ImGui::SameLine();
ImGui::TextDisabled("(ignores your controller)");
}
}
// --- Guest pads the host receives from RPT -----------------------------
ImGui::SeparatorText("Incoming (host receives)");
const auto& pads = input.pads();

View File

@@ -18,9 +18,17 @@ class ControllersPanel
{
public:
// `status` is the hook's back-channel (per-slot poll counters); `debug_details`
// reveals the raw axis values and the per-slot poll-rate table.
// reveals the raw axis values, the per-slot poll-rate table, and the synthetic
// test-input toggle.
void draw(const InputSource& input, const HookStatusView& status, bool debug_details);
// Whether the operator enabled "Forward synthetic test input" (a controller debug
// aid). The host feeds this to InjectionPanel, which substitutes a synthetic pad.
[[nodiscard]] bool test_input() const
{
return test_input_;
}
#ifdef COOP_WITH_STEAM
// Whether the operator has opted into Steam Input. It's off by default: simply
// initializing Steam Input activates Steam's in-process XInput interception,
@@ -44,6 +52,8 @@ public:
#endif
private:
bool test_input_ = false; // "Forward synthetic test input" (debug aid, default off)
// Sampled to turn the hook's cumulative per-slot counters into poll rates.
unsigned long long last_state_count_[kMaxPads] = {};
double state_rate_[kMaxPads] = {};

View File

@@ -584,17 +584,6 @@ void InjectionPanel::draw(bool debug_details)
ImGui::TextColored(status_color_, "%s", status_.c_str());
}
// Synthetic input only reaches the game if the XInput hook is installed and the
// target is actually alive to receive it.
ImGui::BeginDisabled(injected_ && (!want_input_ || target_state_ != TargetState::Alive));
ImGui::Checkbox("Forward synthetic test input", &test_input_);
ImGui::EndDisabled();
if (test_input_)
{
ImGui::SameLine();
ImGui::TextDisabled("(ignores your controller)");
}
draw_hook_status(debug_details);
ImGui::End();

View File

@@ -48,6 +48,13 @@ public:
// test-input mode is on, a synthetic pattern is sent instead of `pads`.
void publish(const std::array<PadInfo, kMaxPads>& pads);
// Enable/disable synthetic test input. The toggle itself lives in the Controllers
// panel (a controller-debug aid); the host feeds its state here each frame.
void set_test_input(bool on)
{
test_input_ = on;
}
// The injected game's main window, as reported by the hook (null if none). A
// terminated target's HWND is stale/invalid, so report none -- the capture and
// audio panels then drop to idle instead of chasing a dead window.

View File

@@ -144,6 +144,7 @@ int run()
}
#endif
input->poll();
injection.set_test_input(controllers.test_input()); // toggle lives in the Controllers panel
injection.publish(input->pads());
injection.tick(); // refresh target liveness before the mirror panels read game_hwnd()
const HWND game = injection.game_hwnd();