Added manual input polling option, should fix menu not working with RTX remix games

This commit is contained in:
cdozdil
2026-05-24 23:23:42 +03:00
parent b5eedb2529
commit 929ef9b1e2
6 changed files with 182 additions and 22 deletions
+6
View File
@@ -1339,6 +1339,12 @@ CheckForUpdate=auto
; true or false - Default (auto) is false, except for OptiFG when true
DisableOverlays=auto
; Use manual input polling instead of hooking WndProc
; Might help games which does not capture inputs properly
; As a downside it will not be able to block inputs when menu is open
; true or false - Default (auto) is false
ManualInputPolling=auto
; Simulate waitable object event signals for FG swapchain
; true or false - Default (auto) is false
SimulateWaitableObject=auto
+3
View File
@@ -561,6 +561,7 @@ bool Config::Reload(std::filesystem::path iniPath)
{
CheckForUpdate.set_from_config(readBool("Hotfix", "CheckForUpdate"));
DisableOverlays.set_from_config(readBool("Hotfix", "DisableOverlays"));
ManualInputPolling.set_from_config(readBool("Hotfix", "ManualInputPolling"));
SimulateWaitableObject.set_from_config(readBool("Hotfix", "SimulateWaitableObject"));
@@ -1199,6 +1200,8 @@ bool Config::SaveIni()
ini.SetValue("Hotfix", "SimulateWaitableObject",
GetBoolValue(Instance()->SimulateWaitableObject.value_for_config()).c_str());
ini.SetValue("Hotfix", "DisableOverlays", GetBoolValue(Instance()->DisableOverlays.value_for_config()).c_str());
ini.SetValue("Hotfix", "ManualInputPolling",
GetBoolValue(Instance()->ManualInputPolling.value_for_config()).c_str());
ini.SetValue("Hotfix", "RoundInternalResolution",
GetIntValue(Instance()->RoundInternalResolution.value_for_config()).c_str());
+1
View File
@@ -350,6 +350,7 @@ class Config
// Hotfixes
CustomOptional<bool> CheckForUpdate { true };
CustomOptional<bool> DisableOverlays { false };
CustomOptional<bool> ManualInputPolling { false };
CustomOptional<bool> SimulateWaitableObject { false };
+10
View File
@@ -1247,6 +1247,9 @@ static void printQuirks(flag_set<GameQuirk>& quirks)
if (quirks & GameQuirk::PregmataFixDLSSModes)
stringQuirks.push_back("Fix DLSS quality selection in Pragmata");
if (quirks & GameQuirk::UseManualInputs)
stringQuirks.push_back("Use manual input polling");
state->detectedQuirks.append_range(stringQuirks);
for (auto& stringQuirk : stringQuirks)
spdlog::info("Quirk: {}", stringQuirk);
@@ -1500,6 +1503,13 @@ static void CheckQuirks()
else
quirks.reset(GameQuirk::OldOverlayMenu);
if (quirks & GameQuirk::UseManualInputs && !Config::Instance()->ManualInputPolling.has_value())
{
Config::Instance()->ManualInputPolling.set_volatile_value(true);
}
else
quirks.reset(GameQuirk::UseManualInputs);
// For Luma, we assume if Luma addon in game folder it's used
const auto dir = Util::ExePath().parent_path();
bool lumaDetected = false;
+160 -21
View File
@@ -38,6 +38,7 @@ static bool inputMenu = false;
static bool inputFG = false;
static bool inputFps = false;
static bool inputFpsCycle = false;
static bool inputManual = false;
static bool hasGamepad = false;
static bool fsr31InitTried = false;
static bool xefgInitTried = false;
@@ -133,6 +134,7 @@ static std::string updateNoticeTag;
static std::string updateNoticeUrl;
static float lastMenuScale = 0.0f;
static CustomOptional<uint32_t> comboPreset { 0 };
static int lastKey = 0;
template <typename T, size_t N> struct RingBuffer
{
@@ -208,6 +210,120 @@ inline std::string StrFmt(const char* fmt, ...)
return out;
}
bool IsKeyReleasedOnce(int vk)
{
static bool previousDown[256] {};
if (vk <= 0 || vk >= 256)
return false;
bool isDown = (GetAsyncKeyState(vk) & 0x8000) != 0;
bool released = previousDown[vk] && !isDown;
previousDown[vk] = isDown;
return released;
}
void UpdateManualInput(HWND targetHwnd)
{
ImGuiIO& io = ImGui::GetIO();
// Only capture input when target window is foreground
HWND foreground = GetForegroundWindow();
bool focused = foreground == targetHwnd;
io.AddFocusEvent(focused);
if (!focused)
{
io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
return;
}
const auto config = Config::Instance();
auto CheckShortcut = [&](int vk, bool& inputFlag, const char* logMessage)
{
if (inputFlag)
return;
if (vk <= 0 || vk >= 256)
return;
if (IsKeyReleasedOnce(vk))
{
lastKey = vk;
receivingWmInputs = false;
inputFlag = true;
LOG_DEBUG("{}", logMessage);
}
};
CheckShortcut(config->ShortcutKey.value_or_default(), inputMenu, "Menu key pressed, will be switching menu");
CheckShortcut(config->FpsShortcutKey.value_or_default(), inputFps, "Menu key pressed, will be switching FPS");
CheckShortcut(config->FGShortcutKey.value_or_default(), inputFG, "Menu key pressed, will be switching FG mode");
CheckShortcut(config->FpsCycleShortcutKey.value_or_default(), inputFpsCycle,
"Menu key pressed, will be switching FPS mode");
// Mouse position
POINT cursorPos {};
GetCursorPos(&cursorPos);
POINT clientPos = cursorPos;
ScreenToClient(targetHwnd, &clientPos);
io.AddMousePosEvent(static_cast<float>(clientPos.x), static_cast<float>(clientPos.y));
// Mouse buttons
io.AddMouseButtonEvent(0, (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0);
io.AddMouseButtonEvent(1, (GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0);
io.AddMouseButtonEvent(2, (GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0);
io.AddMouseButtonEvent(3, (GetAsyncKeyState(VK_XBUTTON1) & 0x8000) != 0);
io.AddMouseButtonEvent(4, (GetAsyncKeyState(VK_XBUTTON2) & 0x8000) != 0);
// Common keyboard keys
auto AddKey = [&](ImGuiKey key, int vk) { io.AddKeyEvent(key, (GetAsyncKeyState(vk) & 0x8000) != 0); };
AddKey(ImGuiKey_Tab, VK_TAB);
AddKey(ImGuiKey_LeftArrow, VK_LEFT);
AddKey(ImGuiKey_RightArrow, VK_RIGHT);
AddKey(ImGuiKey_UpArrow, VK_UP);
AddKey(ImGuiKey_DownArrow, VK_DOWN);
AddKey(ImGuiKey_PageUp, VK_PRIOR);
AddKey(ImGuiKey_PageDown, VK_NEXT);
AddKey(ImGuiKey_Home, VK_HOME);
AddKey(ImGuiKey_End, VK_END);
AddKey(ImGuiKey_Insert, VK_INSERT);
AddKey(ImGuiKey_Delete, VK_DELETE);
AddKey(ImGuiKey_Backspace, VK_BACK);
AddKey(ImGuiKey_Space, VK_SPACE);
AddKey(ImGuiKey_Enter, VK_RETURN);
AddKey(ImGuiKey_Escape, VK_ESCAPE);
AddKey(ImGuiKey_LeftCtrl, VK_LCONTROL);
AddKey(ImGuiKey_LeftShift, VK_LSHIFT);
AddKey(ImGuiKey_LeftAlt, VK_LMENU);
AddKey(ImGuiKey_RightCtrl, VK_RCONTROL);
AddKey(ImGuiKey_RightShift, VK_RSHIFT);
AddKey(ImGuiKey_RightAlt, VK_RMENU);
// Letters
for (int vk = 'A'; vk <= 'Z'; vk++)
{
io.AddKeyEvent(static_cast<ImGuiKey>(ImGuiKey_A + (vk - 'A')), (GetAsyncKeyState(vk) & 0x8000) != 0);
}
// Numbers
for (int vk = '0'; vk <= '9'; vk++)
{
io.AddKeyEvent(static_cast<ImGuiKey>(ImGuiKey_0 + (vk - '0')), (GetAsyncKeyState(vk) & 0x8000) != 0);
}
}
void MenuCommon::ShowTooltip(const char* tip)
{
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
@@ -637,8 +753,6 @@ ImGuiKey MenuCommon::ImGui_ImplWin32_VirtualKeyToImGuiKey(WPARAM wParam)
}
}
static int lastKey = 0;
class Keybind
{
std::string name;
@@ -1874,6 +1988,9 @@ void MenuCommon::Present()
lastFrameTime = now - lastTime;
lastTime = now;
if (inputManual && _handle != nullptr)
UpdateManualInput(_handle);
}
bool MenuCommon::RenderMenu()
@@ -1900,6 +2017,9 @@ bool MenuCommon::RenderMenu()
}
lastTime = now;
if (inputManual && _handle != nullptr)
UpdateManualInput(_handle);
}
else
{
@@ -7193,35 +7313,54 @@ void MenuCommon::Init(HWND InHwnd, bool isUWP)
_hdrTonemapApplied = false;
}
if ((_oWndProc == nullptr || oldHandle != _handle) && !isUWP)
DWORD hwndPid = 0;
DWORD hwndTid = GetWindowThreadProcessId(_handle, &hwndPid);
LOG_DEBUG("HWND: {:X}, IsWindow: {}, HWND PID: {}, Current PID: {}, HWND TID: {}, Current TID: {}",
(ULONG64) _handle, IsWindow(_handle), hwndPid, GetCurrentProcessId(), hwndTid, GetCurrentThreadId());
if (hwndPid == GetCurrentProcessId() && !Config::Instance()->ManualInputPolling.value_or_default())
{
if (oldHandle != nullptr && _oWndProc != nullptr)
inputManual = false;
if ((_oWndProc == nullptr || oldHandle != _handle) && !isUWP)
{
LOG_DEBUG("Restoring old WndProc: {:X}", (ULONG64) _oWndProc);
if (oldHandle != nullptr && _oWndProc != nullptr)
{
LOG_DEBUG("Restoring old WndProc: {:X}", (ULONG64) _oWndProc);
SetLastError(0);
auto restoreResult = SetWindowLongPtr(oldHandle, GWLP_WNDPROC, (LONG_PTR) _oWndProc);
auto error = GetLastError();
if (restoreResult == 0 && error != 0)
{
LOG_ERROR("Failed to restore old WndProc. Error: {:X}", error);
}
}
SetLastError(0);
auto restoreResult = SetWindowLongPtr(oldHandle, GWLP_WNDPROC, (LONG_PTR) _oWndProc);
auto setResult = (WNDPROC) SetWindowLongPtr(_handle, GWLP_WNDPROC, (LONG_PTR) WndProc);
auto error = GetLastError();
if (restoreResult == 0 && error != 0)
if (setResult == nullptr && error != 0)
{
LOG_ERROR("Failed to restore old WndProc. Error: {:X}", error);
LOG_ERROR("Failed to hook WndProc. Error: {:X}", error);
}
else
{
_oWndProc = setResult;
LOG_DEBUG("_oWndProc: {:X}", (ULONG64) _oWndProc);
}
}
}
else
{
LOG_WARN("HWND does not belong to current process,"
" Manual input polling will be used. HWND PID: {}, Current PID: {}",
hwndPid, GetCurrentProcessId());
SetLastError(0);
auto setResult = (WNDPROC) SetWindowLongPtr(_handle, GWLP_WNDPROC, (LONG_PTR) WndProc);
auto error = GetLastError();
if (setResult == nullptr && error != 0)
{
LOG_ERROR("Failed to hook WndProc. Error: {:X}", error);
}
else
{
_oWndProc = setResult;
LOG_DEBUG("_oWndProc: {:X}", (ULONG64) _oWndProc);
}
inputManual = true;
}
if (!pfn_SetCursorPos_hooked)
+2 -1
View File
@@ -42,6 +42,7 @@ enum class GameQuirk : uint64_t
DoNotPreserveFGSwapChain,
DoNotSkipResize,
OldOverlayMenu,
UseManualInputs,
// Quirks that are applied deeper in code
CyberpunkHudlessState,
@@ -280,7 +281,7 @@ static const QuirkEntry quirkTable[] = {
// Metro Exodus Enhanced Edition
// ForceBorderless required to avoid black screen with XeFG
QUIRK_ENTRY("metroexodus.exe", GameQuirk::DisableDxgiSpoofing, GameQuirk::ForceBorderlessWhenUsingXeFG,
GameQuirk::ForceAutoExposure),
GameQuirk::ForceAutoExposure, GameQuirk::UseManualInputs),
// Star Wars: Outlaws
// SL spoof enough to unlock everything DLSS