fix(input): release mouse on stalls and clean quest keybind duplication

This commit is contained in:
Kelsi 2026-03-14 07:29:39 -07:00
parent 013f6be162
commit c1baffadf0
5 changed files with 179 additions and 144 deletions

View file

@ -29,7 +29,6 @@ public:
TOGGLE_WORLD_MAP,
TOGGLE_NAMEPLATES,
TOGGLE_RAID_FRAMES,
TOGGLE_QUEST_LOG,
TOGGLE_ACHIEVEMENTS,
ACTION_COUNT
};

View file

@ -391,8 +391,46 @@ void Application::run() {
}
auto lastTime = std::chrono::high_resolution_clock::now();
std::atomic<bool> watchdogRunning{true};
std::atomic<int64_t> watchdogHeartbeatMs{
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count()
};
std::thread watchdogThread([this, &watchdogRunning, &watchdogHeartbeatMs]() {
bool releasedForCurrentStall = false;
while (watchdogRunning.load(std::memory_order_acquire)) {
std::this_thread::sleep_for(std::chrono::milliseconds(250));
const int64_t nowMs = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count();
const int64_t lastBeatMs = watchdogHeartbeatMs.load(std::memory_order_acquire);
const int64_t stallMs = nowMs - lastBeatMs;
// Failsafe: if the main loop stalls while relative mouse mode is active,
// forcibly release grab so the user can move the cursor and close the app.
if (stallMs > 1500) {
if (!releasedForCurrentStall) {
SDL_SetRelativeMouseMode(SDL_FALSE);
SDL_ShowCursor(SDL_ENABLE);
if (window && window->getSDLWindow()) {
SDL_SetWindowGrab(window->getSDLWindow(), SDL_FALSE);
}
LOG_WARNING("Main-loop stall detected (", stallMs,
"ms) — force-released mouse capture failsafe");
releasedForCurrentStall = true;
}
} else {
releasedForCurrentStall = false;
}
}
});
try {
while (running && !window->shouldClose()) {
watchdogHeartbeatMs.store(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count(),
std::memory_order_release);
// Calculate delta time
auto currentTime = std::chrono::high_resolution_clock::now();
std::chrono::duration<float> deltaTimeDuration = currentTime - lastTime;
@ -529,6 +567,18 @@ void Application::run() {
window->setShouldClose(true);
}
}
} catch (...) {
watchdogRunning.store(false, std::memory_order_release);
if (watchdogThread.joinable()) {
watchdogThread.join();
}
throw;
}
watchdogRunning.store(false, std::memory_order_release);
if (watchdogThread.joinable()) {
watchdogThread.join();
}
LOG_INFO("Main loop ended");
}

View file

@ -2319,7 +2319,11 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
if (!textFocus) {
// Toggle character screen (C) and inventory/bags (I)
if (KeybindingManager::getInstance().isActionPressed(KeybindingManager::Action::TOGGLE_CHARACTER_SCREEN)) {
const bool wasOpen = inventoryScreen.isCharacterOpen();
inventoryScreen.toggleCharacter();
if (!wasOpen && gameHandler.isConnected()) {
gameHandler.requestPlayedTime();
}
}
if (KeybindingManager::getInstance().isActionPressed(KeybindingManager::Action::TOGGLE_INVENTORY)) {
@ -2350,10 +2354,6 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
showRaidFrames_ = !showRaidFrames_;
}
if (KeybindingManager::getInstance().isActionPressed(KeybindingManager::Action::TOGGLE_QUEST_LOG)) {
questLogScreen.toggle();
}
if (KeybindingManager::getInstance().isActionPressed(KeybindingManager::Action::TOGGLE_ACHIEVEMENTS)) {
showAchievementWindow_ = !showAchievementWindow_;
}

View file

@ -743,17 +743,6 @@ void InventoryScreen::render(game::Inventory& inventory, uint64_t moneyCopper) {
bool bToggled = bagsDown && !bKeyWasDown;
bKeyWasDown = bagsDown;
// Character screen toggle (C key, edge-triggered)
bool characterDown = KeybindingManager::getInstance().isActionPressed(
KeybindingManager::Action::TOGGLE_CHARACTER_SCREEN, false);
if (characterDown && !cKeyWasDown) {
characterOpen = !characterOpen;
if (characterOpen && gameHandler_) {
gameHandler_->requestPlayedTime();
}
}
cKeyWasDown = characterDown;
bool wantsTextInput = ImGui::GetIO().WantTextInput;
if (separateBags_) {

View file

@ -35,7 +35,6 @@ void KeybindingManager::initializeDefaults() {
bindings_[static_cast<int>(Action::TOGGLE_WORLD_MAP)] = ImGuiKey_M; // WoW standard: M opens world map
bindings_[static_cast<int>(Action::TOGGLE_NAMEPLATES)] = ImGuiKey_V;
bindings_[static_cast<int>(Action::TOGGLE_RAID_FRAMES)] = ImGuiKey_F; // Reassigned from R (now camera reset)
bindings_[static_cast<int>(Action::TOGGLE_QUEST_LOG)] = ImGuiKey_None; // Q conflicts with strafe-left; quest log accessible via TOGGLE_QUESTS (L)
bindings_[static_cast<int>(Action::TOGGLE_ACHIEVEMENTS)] = ImGuiKey_Y; // WoW standard key (Shift+Y in retail)
}
@ -93,7 +92,6 @@ const char* KeybindingManager::getActionName(Action action) {
case Action::TOGGLE_WORLD_MAP: return "World Map";
case Action::TOGGLE_NAMEPLATES: return "Nameplates";
case Action::TOGGLE_RAID_FRAMES: return "Raid Frames";
case Action::TOGGLE_QUEST_LOG: return "Quest Log";
case Action::TOGGLE_ACHIEVEMENTS: return "Achievements";
case Action::ACTION_COUNT: break;
}
@ -158,7 +156,7 @@ void KeybindingManager::loadFromConfigFile(const std::string& filePath) {
else if (action == "toggle_world_map") actionIdx = static_cast<int>(Action::TOGGLE_WORLD_MAP);
else if (action == "toggle_nameplates") actionIdx = static_cast<int>(Action::TOGGLE_NAMEPLATES);
else if (action == "toggle_raid_frames") actionIdx = static_cast<int>(Action::TOGGLE_RAID_FRAMES);
else if (action == "toggle_quest_log") actionIdx = static_cast<int>(Action::TOGGLE_QUEST_LOG);
else if (action == "toggle_quest_log") actionIdx = static_cast<int>(Action::TOGGLE_QUESTS); // legacy alias
else if (action == "toggle_achievements") actionIdx = static_cast<int>(Action::TOGGLE_ACHIEVEMENTS);
if (actionIdx < 0) continue;
@ -255,7 +253,6 @@ void KeybindingManager::saveToConfigFile(const std::string& filePath) const {
{Action::TOGGLE_WORLD_MAP, "toggle_world_map"},
{Action::TOGGLE_NAMEPLATES, "toggle_nameplates"},
{Action::TOGGLE_RAID_FRAMES, "toggle_raid_frames"},
{Action::TOGGLE_QUEST_LOG, "toggle_quest_log"},
{Action::TOGGLE_ACHIEVEMENTS, "toggle_achievements"},
};