Injection panel UX: reachable auto re-attach + explain disabled controls

- The "Auto re-attach this game on relaunch" checkbox (and the Vulkan-layer
  checkbox) lived inside the connected-only block, so after a disconnect the
  control vanished while auto_reattach_ could stay enabled -- an active,
  invisible flag. Move both to render whenever a target is selected, connected or
  not, so they can be set up pre-launch and toggled off after disconnect.
- Explain why controls are greyed out: an inline hint under the subsystem toggles
  when no live game is connected, and a hover tooltip (AllowWhenDisabled) on the
  disabled "Inject & Connect" button telling the operator to pick a target.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 02:23:11 +02:00
parent e5668d9c48
commit 47be3fa53f
2 changed files with 39 additions and 33 deletions

View File

@@ -113,11 +113,6 @@ default** and covers anything the hooked path doesn't.
From an in-depth review pass. Each item is fixed test-first (a failing test, then the fix) and lands From an in-depth review pass. Each item is fixed test-first (a failing test, then the fix) and lands
as its own commit; "verify" items are confirmed real before any change, and dropped if not. as its own commit; "verify" items are confirmed real before any change, and dropped if not.
UX:
- **Auto re-attach control** — keep it visible/controllable when not connected (today it can be
enabled-but-invisible).
- **Tooltips on disabled controls** — explain *why* Inject/subsystem controls are disabled.
Code quality: Code quality:
- **Stale comments** — `hook_guard.hpp` top block (still the old destroy-on-remove model), - **Stale comments** — `hook_guard.hpp` top block (still the old destroy-on-remove model),
`input_source.hpp` ("future" Steam source), `audio_ring.hpp` (v1/reserved), `shared_memory.hpp` / `input_source.hpp` ("future" Steam source), `audio_ring.hpp` (v1/reserved), `shared_memory.hpp` /

View File

@@ -561,6 +561,10 @@ void InjectionPanel::draw_hook_status(bool debug_details)
ImGui::BeginDisabled(target_state_ != TargetState::Alive); ImGui::BeginDisabled(target_state_ != TargetState::Alive);
draw_subsystem_controls(status); draw_subsystem_controls(status);
ImGui::EndDisabled(); ImGui::EndDisabled();
if (target_state_ != TargetState::Alive)
{
ImGui::TextDisabled("(connect to a live game to change these)"); // why the toggles are locked
}
ImGui::TextDisabled("Controller poll rates are in the Controllers panel."); ImGui::TextDisabled("Controller poll rates are in the Controllers panel.");
draw_hook_list(status); draw_hook_list(status);
@@ -636,21 +640,24 @@ void InjectionPanel::draw(bool debug_details)
ImGui::SameLine(); ImGui::SameLine();
ImGui::TextDisabled("(relaunched %s)", narrow(selected_name_).c_str()); ImGui::TextDisabled("(relaunched %s)", narrow(selected_name_).c_str());
} }
// Session-only auto re-attach: tick it (while attached or terminated), then kill + ImGui::Separator();
// relaunch the game and it re-injects itself early -- the kill+relaunch fix for a }
// wrong audio format, without picking a target again.
// Session options for the selected game, shown whether or not we're connected -- so they can be set
// up before launching the game, and an enabled auto re-attach is never hidden after a disconnect.
if (!selected_name_.empty()) if (!selected_name_.empty())
{ {
// Session-only auto re-attach: tick it, then kill + relaunch the game and it re-injects itself
// early -- the kill+relaunch fix for a wrong audio format, without picking a target again.
ImGui::Checkbox("Auto re-attach this game on relaunch", &auto_reattach_); ImGui::Checkbox("Auto re-attach this game on relaunch", &auto_reattach_);
if (auto_reattach_ && target_state_ == TargetState::Terminated) if (auto_reattach_ && target_state_ == TargetState::Terminated)
{ {
ImGui::SameLine(); ImGui::SameLine();
ImGui::TextColored(kGrey, "(watching for %s...)", narrow(selected_name_).c_str()); ImGui::TextColored(kGrey, "(watching for %s...)", narrow(selected_name_).c_str());
} }
// Opt-in Vulkan capture layer: for Vulkan games that initialize Vulkan immediately // Opt-in Vulkan capture layer: for Vulkan games that initialize Vulkan immediately (where even
// (where even auto-attach injects too late -- see the red banner), register a per-user // auto-attach injects too late -- see the red banner), register a per-user implicit layer scoped
// implicit layer scoped to this game so the next launch is captured from the first frame. // to this game so the next launch is captured from the first frame. Removed when unticked / exit.
// Removed when unticked or the host exits.
if (ImGui::Checkbox("Set up Vulkan layer (for immediate-init Vulkan games)", &vk_layer_enabled_)) if (ImGui::Checkbox("Set up Vulkan layer (for immediate-init Vulkan games)", &vk_layer_enabled_))
{ {
if (vk_layer_enabled_) if (vk_layer_enabled_)
@@ -669,8 +676,6 @@ void InjectionPanel::draw(bool debug_details)
"Auto re-attach. Removed when you untick it or close the tool."); "Auto re-attach. Removed when you untick it or close the tool.");
} }
} }
ImGui::Separator();
}
ImGui::TextUnformatted("Target window"); ImGui::TextUnformatted("Target window");
if (ImGui::Button("Refresh")) if (ImGui::Button("Refresh"))
@@ -737,6 +742,12 @@ void InjectionPanel::draw(bool debug_details)
inject_selected(); inject_selected();
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
// Explain the disabled state on hover (AllowWhenDisabled, since the button is greyed out).
if (!can_inject && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
{
ImGui::SetTooltip("Pick a target window or process above first.\n"
"If the game already has the DLL (e.g. after a reconnect), this reuses it.");
}
if (!status_.empty()) if (!status_.empty())
{ {