From 1fc2481340046d2d6188702efd5fe867a03f5c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Janiszewski?= Date: Sat, 14 Jun 2025 19:04:08 +0200 Subject: [PATCH] Feature: Win32 driver support for gamepad scrolling --- CMakeLists.txt | 1 + src/video/win32_v.cpp | 116 ++++++++++++++++++++++++++++++++++++++++++ src/video/win32_v.h | 8 +++ 3 files changed, 125 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 60f4bc43ff..9fe7b5819a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -449,6 +449,7 @@ if(WIN32) psapi winhttp bcrypt + xinput ) endif() diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp index 798a2dc475..70df9a007e 100644 --- a/src/video/win32_v.cpp +++ b/src/video/win32_v.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #if defined(_MSC_VER) && defined(NTDDI_WIN10_RS4) #include #endif @@ -950,6 +951,9 @@ void VideoDriver_Win32Base::InputLoop() } if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged(); + + /* Process gamepad input for scrolling */ + this->ProcessGamepadInput(); } bool VideoDriver_Win32Base::PollEvent() @@ -1109,6 +1113,106 @@ void VideoDriver_Win32Base::UnlockVideoBuffer() this->buffer_locked = false; } +void VideoDriver_Win32Base::OpenGamepad() +{ + /* Don't open gamepad if already open or if gamepad scrolling is disabled */ + if (this->gamepad_user_index != XUSER_MAX_COUNT) { + Debug(driver, 1, "Win32: Gamepad already open, skipping"); + return; + } + + if (_settings_client.gui.gamepad_stick_selection == GSS_DISABLED) { + Debug(driver, 1, "Win32: Gamepad scrolling disabled, not opening gamepad"); + return; + } + + /* Check for any connected gamepads */ + for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) { + XINPUT_STATE state = {}; + + if (XInputGetState(i, &state) == ERROR_SUCCESS) { + this->gamepad_user_index = i; + Debug(driver, 1, "Win32: Opened gamepad at index {}", i); + return; + } + } +} + +void VideoDriver_Win32Base::CloseGamepad() +{ + if (this->gamepad_user_index != XUSER_MAX_COUNT) { + this->gamepad_user_index = XUSER_MAX_COUNT; + Debug(driver, 1, "Win32: Closed gamepad"); + } +} + +void VideoDriver_Win32Base::ProcessGamepadInput() +{ + /* Skip if gamepad scrolling is disabled */ + if (_settings_client.gui.gamepad_stick_selection == GSS_DISABLED) { + return; + } + + /* If no gamepad is currently open, try to reconnect periodically */ + if (this->gamepad_user_index == XUSER_MAX_COUNT) { + static bool logged_no_gamepad = false; + + /* Only try to reconnect every 60 frames (~1 second at 60 FPS) to avoid spam */ + if (this->gamepad_reconnect_timer > 0) { + this->gamepad_reconnect_timer--; + if (!logged_no_gamepad) { + Debug(driver, 2, "Win32: No gamepad available for input processing"); + logged_no_gamepad = true; + } + return; + } + + /* Try to open gamepad */ + this->OpenGamepad(); + + /* If still no gamepad, set timer for next retry */ + if (this->gamepad_user_index == XUSER_MAX_COUNT) { + this->gamepad_reconnect_timer = 60; /* Retry in ~1 second */ + if (!logged_no_gamepad) { + Debug(driver, 2, "Win32: No gamepad available for input processing"); + logged_no_gamepad = true; + } + return; + } else { + /* Successfully reconnected */ + logged_no_gamepad = false; + } + } + + /* Get gamepad state */ + XINPUT_STATE state = {}; + + if (XInputGetState(this->gamepad_user_index, &state) != ERROR_SUCCESS) { + Debug(driver, 1, "Win32: Gamepad disconnected, closing and will retry connection"); + this->CloseGamepad(); + this->gamepad_reconnect_timer = 60; /* Start retry timer */ + return; + } + + /* Get analog stick values based on stick selection + * Note: XInput uses SHORT values for stick positions, but we have to extend to INT + * to avoid overflow when inverting the Y-axis value */ + INT stick_x = 0, stick_y = 0; + if (_settings_client.gui.gamepad_stick_selection == GSS_LEFT_STICK) { + stick_x = state.Gamepad.sThumbLX; + stick_y = state.Gamepad.sThumbLY; + Debug(driver, 3, "Win32: Left stick raw values: x={}, y={}", stick_x, stick_y); + } else if (_settings_client.gui.gamepad_stick_selection == GSS_RIGHT_STICK) { + stick_x = state.Gamepad.sThumbRX; + stick_y = state.Gamepad.sThumbRY; + Debug(driver, 3, "Win32: Right stick raw values: x={}, y={}", stick_x, stick_y); + } + stick_y = -stick_y; // Xinput Y-axis is inverted from other libraries + + /* Use the common gamepad handling function */ + HandleGamepadScrolling(stick_x, stick_y, 32767); +} + static FVideoDriver_Win32GDI iFVideoDriver_Win32GDI; @@ -1124,6 +1228,11 @@ std::optional VideoDriver_Win32GDI::Start(const StringList &pa MarkWholeScreenDirty(); + /* Initialize gamepad for scrolling */ + Debug(driver, 1, "Win32: Attempting to initialize gamepad support"); + Debug(driver, 1, "Win32: Gamepad stick selection setting: {}", _settings_client.gui.gamepad_stick_selection); + this->OpenGamepad(); + this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread"); return std::nullopt; @@ -1131,6 +1240,7 @@ std::optional VideoDriver_Win32GDI::Start(const StringList &pa void VideoDriver_Win32GDI::Stop() { + this->CloseGamepad(); DeleteObject(this->gdi_palette); DeleteObject(this->dib_sect); @@ -1438,6 +1548,11 @@ std::optional VideoDriver_Win32OpenGL::Start(const StringList MarkWholeScreenDirty(); + /* Initialize gamepad for scrolling */ + Debug(driver, 1, "Win32: Attempting to initialize gamepad support"); + Debug(driver, 1, "Win32: Gamepad stick selection setting: {}", _settings_client.gui.gamepad_stick_selection); + this->OpenGamepad(); + this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread"); return std::nullopt; @@ -1445,6 +1560,7 @@ std::optional VideoDriver_Win32OpenGL::Start(const StringList void VideoDriver_Win32OpenGL::Stop() { + this->CloseGamepad(); this->DestroyContext(); this->VideoDriver_Win32Base::Stop(); } diff --git a/src/video/win32_v.h b/src/video/win32_v.h index 9b2cb8535e..b806aeeadf 100644 --- a/src/video/win32_v.h +++ b/src/video/win32_v.h @@ -14,6 +14,7 @@ #include #include #include +#include /** Base class for Windows video drivers. */ class VideoDriver_Win32Base : public VideoDriver { @@ -48,6 +49,13 @@ protected: bool buffer_locked; ///< Video buffer was locked by the main thread. + /** Gamepad support for map scrolling */ + DWORD gamepad_user_index = XUSER_MAX_COUNT; ///< Index of currently opened gamepad (XUSER_MAX_COUNT = no gamepad). + uint32_t gamepad_reconnect_timer = 0; ///< Timer for retrying gamepad connection after disconnect. + void OpenGamepad(); + void CloseGamepad(); + void ProcessGamepadInput(); + Dimension GetScreenSize() const override; float GetDPIScale() override; void InputLoop() override;