1
0
Fork 0

Feature: Win32 driver support for gamepad scrolling

Michał Janiszewski 2025-06-14 19:04:08 +02:00
parent 55022a0fe2
commit b2c9c803c3
3 changed files with 125 additions and 0 deletions

View File

@ -449,6 +449,7 @@ if(WIN32)
psapi
winhttp
bcrypt
xinput
)
endif()

View File

@ -28,6 +28,7 @@
#include <windows.h>
#include <imm.h>
#include <versionhelpers.h>
#include <xinput.h>
#if defined(_MSC_VER) && defined(NTDDI_WIN10_RS4)
#include <winrt/Windows.UI.ViewManagement.h>
#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<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> VideoDriver_Win32OpenGL::Start(const StringList
void VideoDriver_Win32OpenGL::Stop()
{
this->CloseGamepad();
this->DestroyContext();
this->VideoDriver_Win32Base::Stop();
}

View File

@ -14,6 +14,7 @@
#include <mutex>
#include <condition_variable>
#include <windows.h>
#include <xinput.h>
/** 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;