From 574afdf53d5534fdfaf95def252d7667bd08d76a Mon Sep 17 00:00:00 2001 From: "Al. Lopez" <67606569+AL2009man@users.noreply.github.com> Date: Tue, 4 Mar 2025 17:03:02 -0500 Subject: [PATCH 01/15] Experimenting Button Label Experimenting a way to add a hint for Button Labels. It should attempt to fix the Nintendo Layout issue when using a Nintendo Switch controller --- UnleashedRecomp/hid/driver/sdl_hid.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/UnleashedRecomp/hid/driver/sdl_hid.cpp b/UnleashedRecomp/hid/driver/sdl_hid.cpp index 85e6793a..f76ead5d 100644 --- a/UnleashedRecomp/hid/driver/sdl_hid.cpp +++ b/UnleashedRecomp/hid/driver/sdl_hid.cpp @@ -310,6 +310,7 @@ void hid::Init() SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII, "1"); SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "1"); + SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); SDL_InitSubSystem(SDL_INIT_EVENTS); SDL_AddEventWatch(HID_OnSDLEvent, nullptr); From 07d193e229ddd101af5e5014180530cc3acbcf86 Mon Sep 17 00:00:00 2001 From: "Al. Lopez" <67606569+AL2009man@users.noreply.github.com> Date: Tue, 4 Mar 2025 22:37:09 -0500 Subject: [PATCH 02/15] Create c-cpp.yml --- .github/workflows/c-cpp.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/c-cpp.yml diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml new file mode 100644 index 00000000..6a9c312e --- /dev/null +++ b/.github/workflows/c-cpp.yml @@ -0,0 +1,23 @@ +name: C/C++ CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: configure + run: ./configure + - name: make + run: make + - name: make check + run: make check + - name: make distcheck + run: make distcheck From 176bb625e2ac54b6678f55def6e634fbf09b410a Mon Sep 17 00:00:00 2001 From: "Al. Lopez" <67606569+AL2009man@users.noreply.github.com> Date: Tue, 4 Mar 2025 22:37:24 -0500 Subject: [PATCH 03/15] Create cmake-multi-platform.yml --- .github/workflows/cmake-multi-platform.yml | 75 ++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .github/workflows/cmake-multi-platform.yml diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml new file mode 100644 index 00000000..7ab1b4d7 --- /dev/null +++ b/.github/workflows/cmake-multi-platform.yml @@ -0,0 +1,75 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +name: CMake on multiple platforms + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + # Set up a matrix to run the following 3 configurations: + # 1. + # 2. + # 3. + # + # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + matrix: + os: [ubuntu-latest, windows-latest] + build_type: [Release] + c_compiler: [gcc, clang, cl] + include: + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + - os: ubuntu-latest + c_compiler: gcc + cpp_compiler: g++ + - os: ubuntu-latest + c_compiler: clang + cpp_compiler: clang++ + exclude: + - os: windows-latest + c_compiler: gcc + - os: windows-latest + c_compiler: clang + - os: ubuntu-latest + c_compiler: cl + + steps: + - uses: actions/checkout@v4 + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -S ${{ github.workspace }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} + + - name: Test + working-directory: ${{ steps.strings.outputs.build-output-dir }} + # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --build-config ${{ matrix.build_type }} From 9a181e0839d6d81ae2c043d8221598f05c78a5be Mon Sep 17 00:00:00 2001 From: "Al. Lopez" <67606569+AL2009man@users.noreply.github.com> Date: Tue, 4 Mar 2025 22:40:40 -0500 Subject: [PATCH 04/15] Create apply-patch.yml --- .github/workflows/apply-patch.yml | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/apply-patch.yml diff --git a/.github/workflows/apply-patch.yml b/.github/workflows/apply-patch.yml new file mode 100644 index 00000000..45c30ce1 --- /dev/null +++ b/.github/workflows/apply-patch.yml @@ -0,0 +1,40 @@ +name: Apply Patch + +on: [push] + +jobs: + apply_patch: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Apply patch + run: | + git apply < Date: Tue, 4 Mar 2025 22:48:07 -0500 Subject: [PATCH 05/15] removing the workflows --- .github/workflows/apply-patch.yml | 40 ------------ .github/workflows/c-cpp.yml | 23 ------- .github/workflows/cmake-multi-platform.yml | 75 ---------------------- 3 files changed, 138 deletions(-) delete mode 100644 .github/workflows/apply-patch.yml delete mode 100644 .github/workflows/c-cpp.yml delete mode 100644 .github/workflows/cmake-multi-platform.yml diff --git a/.github/workflows/apply-patch.yml b/.github/workflows/apply-patch.yml deleted file mode 100644 index 45c30ce1..00000000 --- a/.github/workflows/apply-patch.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Apply Patch - -on: [push] - -jobs: - apply_patch: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Apply patch - run: | - git apply < - # 2. - # 3. - # - # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. - matrix: - os: [ubuntu-latest, windows-latest] - build_type: [Release] - c_compiler: [gcc, clang, cl] - include: - - os: windows-latest - c_compiler: cl - cpp_compiler: cl - - os: ubuntu-latest - c_compiler: gcc - cpp_compiler: g++ - - os: ubuntu-latest - c_compiler: clang - cpp_compiler: clang++ - exclude: - - os: windows-latest - c_compiler: gcc - - os: windows-latest - c_compiler: clang - - os: ubuntu-latest - c_compiler: cl - - steps: - - uses: actions/checkout@v4 - - - name: Set reusable strings - # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. - id: strings - shell: bash - run: | - echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - - - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: > - cmake -B ${{ steps.strings.outputs.build-output-dir }} - -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} - -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -S ${{ github.workspace }} - - - name: Build - # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). - run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} - - - name: Test - working-directory: ${{ steps.strings.outputs.build-output-dir }} - # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --build-config ${{ matrix.build_type }} From bec2cf65ba4f06cb50688eb40d83e51af6793e2b Mon Sep 17 00:00:00 2001 From: "Al. Lopez" <67606569+AL2009man@users.noreply.github.com> Date: Thu, 6 Mar 2025 01:59:35 -0500 Subject: [PATCH 06/15] added experimental Steam Virtual Gamepad support --- UnleashedRecomp/hid/driver/sdl_hid.cpp | 21 ++++++++++++++------- UnleashedRecomp/hid/hid.h | 3 ++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/UnleashedRecomp/hid/driver/sdl_hid.cpp b/UnleashedRecomp/hid/driver/sdl_hid.cpp index f76ead5d..fa09ffe4 100644 --- a/UnleashedRecomp/hid/driver/sdl_hid.cpp +++ b/UnleashedRecomp/hid/driver/sdl_hid.cpp @@ -38,20 +38,23 @@ class Controller SDL_GameControllerType GetControllerType() const { - return SDL_GameControllerTypeForIndex(index); + return SDL_GameControllerGetType(controller); } hid::EInputDevice GetInputDevice() const { switch (GetControllerType()) { - case SDL_CONTROLLER_TYPE_PS3: - case SDL_CONTROLLER_TYPE_PS4: - case SDL_CONTROLLER_TYPE_PS5: - return hid::EInputDevice::PlayStation; + case SDL_CONTROLLER_TYPE_PS3: + case SDL_CONTROLLER_TYPE_PS4: + case SDL_CONTROLLER_TYPE_PS5: + return hid::EInputDevice::PlayStation; + case SDL_CONTROLLER_TYPE_XBOX360: + case SDL_CONTROLLER_TYPE_XBOXONE: + return hid::EInputDevice::Xbox; + default: + return hid::EInputDevice::Unknown; } - - return hid::EInputDevice::Xbox; } void Close() @@ -134,6 +137,7 @@ class Controller } }; + std::array g_controllers; Controller* g_activeController; @@ -310,6 +314,8 @@ void hid::Init() SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII, "1"); SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); + SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); SDL_InitSubSystem(SDL_INIT_EVENTS); @@ -318,6 +324,7 @@ void hid::Init() SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); } + uint32_t hid::GetState(uint32_t dwUserIndex, XAMINPUT_STATE* pState) { static uint32_t packet; diff --git a/UnleashedRecomp/hid/hid.h b/UnleashedRecomp/hid/hid.h index 730694a9..28736f09 100644 --- a/UnleashedRecomp/hid/hid.h +++ b/UnleashedRecomp/hid/hid.h @@ -7,7 +7,8 @@ namespace hid Keyboard, Mouse, Xbox, - PlayStation + PlayStation, + Unknown }; enum class EInputDeviceExplicit From ef4e37cb417429a2ce7287ba61fd23d13ce3f9fd Mon Sep 17 00:00:00 2001 From: AL2009man <67606569+AL2009man@users.noreply.github.com> Date: Thu, 6 Mar 2025 02:16:09 -0500 Subject: [PATCH 07/15] restoring notes for Button Labels. --- UnleashedRecomp/hid/driver/sdl_hid.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/UnleashedRecomp/hid/driver/sdl_hid.cpp b/UnleashedRecomp/hid/driver/sdl_hid.cpp index fa09ffe4..5f827204 100644 --- a/UnleashedRecomp/hid/driver/sdl_hid.cpp +++ b/UnleashedRecomp/hid/driver/sdl_hid.cpp @@ -314,9 +314,8 @@ void hid::Init() SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII, "1"); SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); - SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); + SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); // Uses Button Labels. This hint is disabled for Nintendo Controllers. SDL_InitSubSystem(SDL_INIT_EVENTS); SDL_AddEventWatch(HID_OnSDLEvent, nullptr); From c9b3a5e03f9e5a33b0f6fcd281b2ead378fbc8ec Mon Sep 17 00:00:00 2001 From: AL2009man <67606569+AL2009man@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:44:40 -0500 Subject: [PATCH 08/15] Initial Gamepad Hotplug Logic improvements This changes the way how a Controller will be prioritized. By default: it'll always prioritize based on Player 1. It should play nicely with Steam Deck's internal inputs when Steam Input is active --- UnleashedRecomp/hid/driver/sdl_hid.cpp | 152 +++++++++++++++---------- 1 file changed, 89 insertions(+), 63 deletions(-) diff --git a/UnleashedRecomp/hid/driver/sdl_hid.cpp b/UnleashedRecomp/hid/driver/sdl_hid.cpp index 5f827204..4b3ee713 100644 --- a/UnleashedRecomp/hid/driver/sdl_hid.cpp +++ b/UnleashedRecomp/hid/driver/sdl_hid.cpp @@ -197,111 +197,137 @@ static void SetControllerTimeOfDayLED(Controller& controller, bool isNight) auto g = isNight ? 0 : 37; auto b = isNight ? 101 : 184; - controller.SetLED(r, g, b); + // Ensure the lightbar is set correctly + if (SDL_GameControllerHasLED(controller.controller)) + { + SDL_GameControllerSetLED(controller.controller, r, g, b); + } } + int HID_OnSDLEvent(void*, SDL_Event* event) { switch (event->type) { - case SDL_CONTROLLERDEVICEADDED: + case SDL_CONTROLLERDEVICEADDED: + { + const auto freeIndex = FindFreeController(); + + if (freeIndex != -1) { - const auto freeIndex = FindFreeController(); + auto controller = Controller(event->cdevice.which); - if (freeIndex != -1) - { - auto controller = Controller(event->cdevice.which); + g_controllers[freeIndex] = controller; - g_controllers[freeIndex] = controller; + SetControllerTimeOfDayLED(controller, App::s_isWerehog); - SetControllerTimeOfDayLED(controller, App::s_isWerehog); + // Ensure Player 1's controller is always the active controller + if (freeIndex == 0) + { + SetControllerInputDevice(&g_controllers[0]); } - - break; } - case SDL_CONTROLLERDEVICEREMOVED: - { - auto* controller = FindController(event->cdevice.which); + break; + } - if (controller) - controller->Close(); + case SDL_CONTROLLERDEVICEREMOVED: + { + auto* controller = FindController(event->cdevice.which); - break; - } + if (controller) + controller->Close(); - case SDL_CONTROLLERBUTTONDOWN: - case SDL_CONTROLLERBUTTONUP: - case SDL_CONTROLLERAXISMOTION: - case SDL_CONTROLLERTOUCHPADDOWN: + // If Player 1's controller is removed, set the next available controller as active + if (controller == &g_controllers[0]) { - auto* controller = FindController(event->cdevice.which); - - if (!controller) - break; - - if (event->type == SDL_CONTROLLERAXISMOTION) + for (auto& ctrl : g_controllers) { - if (abs(event->caxis.value) > 8000) + if (ctrl.CanPoll()) { - SDL_ShowCursor(SDL_DISABLE); - SetControllerInputDevice(controller); + SetControllerInputDevice(&ctrl); + break; } - - controller->PollAxis(); } - else + } + + break; + } + + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + case SDL_CONTROLLERAXISMOTION: + case SDL_CONTROLLERTOUCHPADDOWN: + { + auto* controller = FindController(event->cdevice.which); + + if (!controller) + break; + + if (event->type == SDL_CONTROLLERAXISMOTION) + { + if (abs(event->caxis.value) > 8000) { SDL_ShowCursor(SDL_DISABLE); SetControllerInputDevice(controller); - - controller->Poll(); } - break; + controller->PollAxis(); } + else + { + SDL_ShowCursor(SDL_DISABLE); + SetControllerInputDevice(controller); - case SDL_KEYDOWN: - case SDL_KEYUP: - hid::g_inputDevice = hid::EInputDevice::Keyboard; - break; + controller->Poll(); + } - case SDL_MOUSEMOTION: - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - { - if (!GameWindow::IsFullscreen() || GameWindow::s_isFullscreenCursorVisible) - SDL_ShowCursor(SDL_ENABLE); + break; + } - hid::g_inputDevice = hid::EInputDevice::Mouse; + case SDL_KEYDOWN: + case SDL_KEYUP: + hid::g_inputDevice = hid::EInputDevice::Keyboard; + break; - break; - } + case SDL_MOUSEMOTION: + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + if (!GameWindow::IsFullscreen() || GameWindow::s_isFullscreenCursorVisible) + SDL_ShowCursor(SDL_ENABLE); - case SDL_WINDOWEVENT: - { - if (event->window.event == SDL_WINDOWEVENT_FOCUS_LOST) - { - // Stop vibrating controllers on focus lost. - for (auto& controller : g_controllers) - controller.SetVibration({ 0, 0 }); - } + hid::g_inputDevice = hid::EInputDevice::Mouse; - break; - } + break; + } - case SDL_USER_EVILSONIC: + case SDL_WINDOWEVENT: + { + if (event->window.event == SDL_WINDOWEVENT_FOCUS_LOST) { + // Stop vibrating controllers on focus lost. for (auto& controller : g_controllers) - SetControllerTimeOfDayLED(controller, event->user.code); - - break; + controller.SetVibration({ 0, 0 }); } + + break; + } + + case SDL_USER_EVILSONIC: + { + for (auto& controller : g_controllers) + SetControllerTimeOfDayLED(controller, event->user.code); + + break; + } } return 0; } + + void hid::Init() { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); From a96fc60dcdfcd705f1d9875758410901e2490b24 Mon Sep 17 00:00:00 2001 From: AL2009man <67606569+AL2009man@users.noreply.github.com> Date: Thu, 6 Mar 2025 19:45:13 -0500 Subject: [PATCH 09/15] Lightbar detection when using multiple PlayStation controllers at the same time. An attempt to remedy the Lightbar activation. While Player 2/3/4 will override the in-game lightbar event upon connection: it'll later revert back to the in-game event. --- UnleashedRecomp/hid/driver/sdl_hid.cpp | 43 +++++++++++++++++--------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/UnleashedRecomp/hid/driver/sdl_hid.cpp b/UnleashedRecomp/hid/driver/sdl_hid.cpp index 4b3ee713..dfb8e379 100644 --- a/UnleashedRecomp/hid/driver/sdl_hid.cpp +++ b/UnleashedRecomp/hid/driver/sdl_hid.cpp @@ -10,6 +10,13 @@ #define TRANSLATE_INPUT(S, X) SDL_GameControllerGetButton(controller, S) << FirstBitLow(X) #define VIBRATION_TIMEOUT_MS 5000 +struct LEDColor +{ + uint8_t r; + uint8_t g; + uint8_t b; +}; + class Controller { public: @@ -19,6 +26,7 @@ class Controller XAMINPUT_GAMEPAD state{}; XAMINPUT_VIBRATION vibration{ 0, 0 }; int index{}; + LEDColor ledColor{ 0, 0, 0 }; Controller() = default; @@ -131,13 +139,16 @@ class Controller SDL_GameControllerRumble(controller, vibration.wLeftMotorSpeed * 256, vibration.wRightMotorSpeed * 256, VIBRATION_TIMEOUT_MS); } - void SetLED(const uint8_t r, const uint8_t g, const uint8_t b) const + void SetLED(const uint8_t r, const uint8_t g, const uint8_t b) { - SDL_GameControllerSetLED(controller, r, g, b); + if (SDL_GameControllerHasLED(controller)) + { + SDL_GameControllerSetLED(controller, r, g, b); + ledColor = { r, g, b }; + } } }; - std::array g_controllers; Controller* g_activeController; @@ -197,14 +208,20 @@ static void SetControllerTimeOfDayLED(Controller& controller, bool isNight) auto g = isNight ? 0 : 37; auto b = isNight ? 101 : 184; - // Ensure the lightbar is set correctly - if (SDL_GameControllerHasLED(controller.controller)) + controller.SetLED(r, g, b); +} + +static void UpdateAllControllerLEDs(bool isNight) +{ + for (auto& controller : g_controllers) { - SDL_GameControllerSetLED(controller.controller, r, g, b); + if (controller.CanPoll()) + { + SetControllerTimeOfDayLED(controller, isNight); + } } } - int HID_OnSDLEvent(void*, SDL_Event* event) { switch (event->type) @@ -219,7 +236,8 @@ int HID_OnSDLEvent(void*, SDL_Event* event) g_controllers[freeIndex] = controller; - SetControllerTimeOfDayLED(controller, App::s_isWerehog); + // Reapply the stored LED color state + controller.SetLED(controller.ledColor.r, controller.ledColor.g, controller.ledColor.b); // Ensure Player 1's controller is always the active controller if (freeIndex == 0) @@ -316,8 +334,8 @@ int HID_OnSDLEvent(void*, SDL_Event* event) case SDL_USER_EVILSONIC: { - for (auto& controller : g_controllers) - SetControllerTimeOfDayLED(controller, event->user.code); + // Update all controller LEDs to follow Player 1's LED + UpdateAllControllerLEDs(event->user.code); break; } @@ -326,8 +344,6 @@ int HID_OnSDLEvent(void*, SDL_Event* event) return 0; } - - void hid::Init() { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); @@ -336,7 +352,7 @@ void hid::Init() SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, "0"); // Disable Player LED for PS5 controllers SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII, "1"); SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "1"); @@ -349,7 +365,6 @@ void hid::Init() SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); } - uint32_t hid::GetState(uint32_t dwUserIndex, XAMINPUT_STATE* pState) { static uint32_t packet; From 458938c2ae1f3154c6919fc6e865fdeeb2232e0f Mon Sep 17 00:00:00 2001 From: AL2009man <67606569+AL2009man@users.noreply.github.com> Date: Thu, 6 Mar 2025 20:09:24 -0500 Subject: [PATCH 10/15] Attempt to reduce Input leaking To avoid "the Controller is leaking" situation, there's now a Gamepad stat management that should reset Controller state based on connected controller. --- UnleashedRecomp/hid/driver/sdl_hid.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/UnleashedRecomp/hid/driver/sdl_hid.cpp b/UnleashedRecomp/hid/driver/sdl_hid.cpp index dfb8e379..97df8fb5 100644 --- a/UnleashedRecomp/hid/driver/sdl_hid.cpp +++ b/UnleashedRecomp/hid/driver/sdl_hid.cpp @@ -82,6 +82,11 @@ class Controller return controller; } + void ClearState() + { + memset(&state, 0, sizeof(state)); + } + void PollAxis() { if (!CanPoll()) @@ -184,6 +189,11 @@ inline Controller* FindController(int which) static void SetControllerInputDevice(Controller* controller) { + if (g_activeController && g_activeController != controller) + { + g_activeController->ClearState(); + } + g_activeController = controller; if (App::s_isLoading) @@ -415,3 +425,4 @@ uint32_t hid::GetCapabilities(uint32_t dwUserIndex, XAMINPUT_CAPABILITIES* pCaps return ERROR_SUCCESS; } + From 75dacf5578b74f7c6ce7145ba2a43bf8e039dde0 Mon Sep 17 00:00:00 2001 From: AL2009man <67606569+AL2009man@users.noreply.github.com> Date: Thu, 6 Mar 2025 20:44:15 -0500 Subject: [PATCH 11/15] Lightbar active fix when gamepad plugged first prior to game launch Another attempt to fix the lightbar by redoing the controller state mangement. For some reason: the Lightbar gets disabled when a controller is already plugged prior to game launch. It reverts back to normal until the next game event. --- UnleashedRecomp/hid/driver/sdl_hid.cpp | 45 +++++++++----------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/UnleashedRecomp/hid/driver/sdl_hid.cpp b/UnleashedRecomp/hid/driver/sdl_hid.cpp index 97df8fb5..3ca65ac2 100644 --- a/UnleashedRecomp/hid/driver/sdl_hid.cpp +++ b/UnleashedRecomp/hid/driver/sdl_hid.cpp @@ -10,13 +10,6 @@ #define TRANSLATE_INPUT(S, X) SDL_GameControllerGetButton(controller, S) << FirstBitLow(X) #define VIBRATION_TIMEOUT_MS 5000 -struct LEDColor -{ - uint8_t r; - uint8_t g; - uint8_t b; -}; - class Controller { public: @@ -26,7 +19,6 @@ class Controller XAMINPUT_GAMEPAD state{}; XAMINPUT_VIBRATION vibration{ 0, 0 }; int index{}; - LEDColor ledColor{ 0, 0, 0 }; Controller() = default; @@ -87,6 +79,7 @@ class Controller memset(&state, 0, sizeof(state)); } + void PollAxis() { if (!CanPoll()) @@ -144,16 +137,13 @@ class Controller SDL_GameControllerRumble(controller, vibration.wLeftMotorSpeed * 256, vibration.wRightMotorSpeed * 256, VIBRATION_TIMEOUT_MS); } - void SetLED(const uint8_t r, const uint8_t g, const uint8_t b) + void SetLED(const uint8_t r, const uint8_t g, const uint8_t b) const { - if (SDL_GameControllerHasLED(controller)) - { - SDL_GameControllerSetLED(controller, r, g, b); - ledColor = { r, g, b }; - } + SDL_GameControllerSetLED(controller, r, g, b); } }; + std::array g_controllers; Controller* g_activeController; @@ -218,20 +208,14 @@ static void SetControllerTimeOfDayLED(Controller& controller, bool isNight) auto g = isNight ? 0 : 37; auto b = isNight ? 101 : 184; - controller.SetLED(r, g, b); -} - -static void UpdateAllControllerLEDs(bool isNight) -{ - for (auto& controller : g_controllers) + // Ensure the lightbar is set correctly + if (SDL_GameControllerHasLED(controller.controller)) { - if (controller.CanPoll()) - { - SetControllerTimeOfDayLED(controller, isNight); - } + SDL_GameControllerSetLED(controller.controller, r, g, b); } } + int HID_OnSDLEvent(void*, SDL_Event* event) { switch (event->type) @@ -246,8 +230,7 @@ int HID_OnSDLEvent(void*, SDL_Event* event) g_controllers[freeIndex] = controller; - // Reapply the stored LED color state - controller.SetLED(controller.ledColor.r, controller.ledColor.g, controller.ledColor.b); + SetControllerTimeOfDayLED(controller, App::s_isWerehog); // Ensure Player 1's controller is always the active controller if (freeIndex == 0) @@ -344,8 +327,8 @@ int HID_OnSDLEvent(void*, SDL_Event* event) case SDL_USER_EVILSONIC: { - // Update all controller LEDs to follow Player 1's LED - UpdateAllControllerLEDs(event->user.code); + for (auto& controller : g_controllers) + SetControllerTimeOfDayLED(controller, event->user.code); break; } @@ -354,6 +337,8 @@ int HID_OnSDLEvent(void*, SDL_Event* event) return 0; } + + void hid::Init() { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); @@ -362,7 +347,7 @@ void hid::Init() SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, "0"); // Disable Player LED for PS5 controllers + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII, "1"); SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "1"); @@ -375,6 +360,7 @@ void hid::Init() SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); } + uint32_t hid::GetState(uint32_t dwUserIndex, XAMINPUT_STATE* pState) { static uint32_t packet; @@ -425,4 +411,3 @@ uint32_t hid::GetCapabilities(uint32_t dwUserIndex, XAMINPUT_CAPABILITIES* pCaps return ERROR_SUCCESS; } - From aedc26bf70de1d3d15f159aee2705af49cab3caf Mon Sep 17 00:00:00 2001 From: AL2009man <67606569+AL2009man@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:11:08 -0400 Subject: [PATCH 12/15] Rebase PR branch to avoid conflictions with other PRs (https://github.com/hedge-dev/UnleashedRecomp/pull/1086 and https://github.com/hedge-dev/UnleashedRecomp/pull/1045): the remaining codebae was restore to how the master build look. the downside is that it makes it far harder to test it's behavior with Steam Input mode. but this is only temporarily until it gets added to the Master branch. --- UnleashedRecomp/hid/driver/sdl_hid.cpp | 89 ++++++++++++-------------- 1 file changed, 40 insertions(+), 49 deletions(-) diff --git a/UnleashedRecomp/hid/driver/sdl_hid.cpp b/UnleashedRecomp/hid/driver/sdl_hid.cpp index 3ca65ac2..175a865d 100644 --- a/UnleashedRecomp/hid/driver/sdl_hid.cpp +++ b/UnleashedRecomp/hid/driver/sdl_hid.cpp @@ -38,23 +38,20 @@ class Controller SDL_GameControllerType GetControllerType() const { - return SDL_GameControllerGetType(controller); + return SDL_GameControllerTypeForIndex(index); } hid::EInputDevice GetInputDevice() const { switch (GetControllerType()) { - case SDL_CONTROLLER_TYPE_PS3: - case SDL_CONTROLLER_TYPE_PS4: - case SDL_CONTROLLER_TYPE_PS5: - return hid::EInputDevice::PlayStation; - case SDL_CONTROLLER_TYPE_XBOX360: - case SDL_CONTROLLER_TYPE_XBOXONE: - return hid::EInputDevice::Xbox; - default: - return hid::EInputDevice::Unknown; + case SDL_CONTROLLER_TYPE_PS3: + case SDL_CONTROLLER_TYPE_PS4: + case SDL_CONTROLLER_TYPE_PS5: + return hid::EInputDevice::PlayStation; } + + return hid::EInputDevice::Xbox; } void Close() @@ -74,12 +71,6 @@ class Controller return controller; } - void ClearState() - { - memset(&state, 0, sizeof(state)); - } - - void PollAxis() { if (!CanPoll()) @@ -143,7 +134,6 @@ class Controller } }; - std::array g_controllers; Controller* g_activeController; @@ -179,11 +169,6 @@ inline Controller* FindController(int which) static void SetControllerInputDevice(Controller* controller) { - if (g_activeController && g_activeController != controller) - { - g_activeController->ClearState(); - } - g_activeController = controller; if (App::s_isLoading) @@ -204,18 +189,27 @@ static void SetControllerInputDevice(Controller* controller) static void SetControllerTimeOfDayLED(Controller& controller, bool isNight) { + // Determine the lightbar color based on night of day. auto r = isNight ? 22 : 0; auto g = isNight ? 0 : 37; auto b = isNight ? 101 : 184; - // Ensure the lightbar is set correctly - if (SDL_GameControllerHasLED(controller.controller)) + // Set the LED for the given controller + controller.SetLED(r, g, b); + + // Ensure all other controllers mirror Player 1's lightbar + if (controller.controller == g_controllers[0].controller) // Check if it's Player 1 { - SDL_GameControllerSetLED(controller.controller, r, g, b); + for (auto& ctrl : g_controllers) + { + if (ctrl.controller != g_controllers[0].controller) // Skip Player 1 itself + { + ctrl.SetLED(r, g, b); // Mirror Player 1's lightbar + } + } } } - int HID_OnSDLEvent(void*, SDL_Event* event) { switch (event->type) @@ -227,18 +221,14 @@ int HID_OnSDLEvent(void*, SDL_Event* event) if (freeIndex != -1) { auto controller = Controller(event->cdevice.which); - g_controllers[freeIndex] = controller; - SetControllerTimeOfDayLED(controller, App::s_isWerehog); + // Use App::s_isWerehog to determine if it is night or day + SetControllerTimeOfDayLED(g_controllers[0], App::s_isWerehog); - // Ensure Player 1's controller is always the active controller - if (freeIndex == 0) - { - SetControllerInputDevice(&g_controllers[0]); - } + // Enforce Player 1's lightbar on the newly added controller instantly + SetControllerTimeOfDayLED(controller, App::s_isWerehog); } - break; } @@ -247,21 +237,26 @@ int HID_OnSDLEvent(void*, SDL_Event* event) auto* controller = FindController(event->cdevice.which); if (controller) + { controller->Close(); - // If Player 1's controller is removed, set the next available controller as active - if (controller == &g_controllers[0]) - { - for (auto& ctrl : g_controllers) + // If Player 1's controller is removed, set the next available controller as active + if (controller == &g_controllers[0]) { - if (ctrl.CanPoll()) + for (auto& ctrl : g_controllers) { - SetControllerInputDevice(&ctrl); - break; + if (ctrl.CanPoll()) + { + SetControllerInputDevice(&ctrl); + g_controllers[0] = ctrl; + + // Reapply the lightbar color to ensure custom in-game settings + SetControllerTimeOfDayLED(ctrl, App::s_isWerehog); + break; + } } } } - break; } @@ -293,6 +288,8 @@ int HID_OnSDLEvent(void*, SDL_Event* event) controller->Poll(); } + // Reapply the lightbar color to override system changes during input events + SetControllerTimeOfDayLED(*controller, App::s_isWerehog); break; } @@ -309,7 +306,6 @@ int HID_OnSDLEvent(void*, SDL_Event* event) SDL_ShowCursor(SDL_ENABLE); hid::g_inputDevice = hid::EInputDevice::Mouse; - break; } @@ -321,12 +317,12 @@ int HID_OnSDLEvent(void*, SDL_Event* event) for (auto& controller : g_controllers) controller.SetVibration({ 0, 0 }); } - break; } case SDL_USER_EVILSONIC: { + // Refresh all controllers to ensure consistent lightbar colors for (auto& controller : g_controllers) SetControllerTimeOfDayLED(controller, event->user.code); @@ -337,8 +333,6 @@ int HID_OnSDLEvent(void*, SDL_Event* event) return 0; } - - void hid::Init() { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); @@ -352,15 +346,12 @@ void hid::Init() SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII, "1"); SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "1"); - SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); // Uses Button Labels. This hint is disabled for Nintendo Controllers. - SDL_InitSubSystem(SDL_INIT_EVENTS); SDL_AddEventWatch(HID_OnSDLEvent, nullptr); SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); } - uint32_t hid::GetState(uint32_t dwUserIndex, XAMINPUT_STATE* pState) { static uint32_t packet; From 8758808d6e7d83df532cceda33ca410af73bd328 Mon Sep 17 00:00:00 2001 From: AL2009man <67606569+AL2009man@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:48:40 -0400 Subject: [PATCH 13/15] Improved Virtual Gamepad and Lightbar/Player LED detection When Player 0 (which is typically a Virtual Controller, or Onboard Inputs like Steam Deck under Steam Input mode): it'll attempt to provide or change Player LED numbers or lightbar (in DualShock 4's case). Now: Player LED Index color will only apply if it's explificially "Player 1" and not "Player 0". it also improves PlayStation lightbar when hotswapping controllers. it should now follow Player 1's Lightbar (due to being tied to Player 1) instead of doing it's own thing. (You REALLY should check your connected controller!) --- UnleashedRecomp/hid/driver/sdl_hid.cpp | 38 ++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/UnleashedRecomp/hid/driver/sdl_hid.cpp b/UnleashedRecomp/hid/driver/sdl_hid.cpp index 175a865d..f4c69483 100644 --- a/UnleashedRecomp/hid/driver/sdl_hid.cpp +++ b/UnleashedRecomp/hid/driver/sdl_hid.cpp @@ -220,14 +220,32 @@ int HID_OnSDLEvent(void*, SDL_Event* event) if (freeIndex != -1) { + // Initialize the new controller auto controller = Controller(event->cdevice.which); g_controllers[freeIndex] = controller; - // Use App::s_isWerehog to determine if it is night or day - SetControllerTimeOfDayLED(g_controllers[0], App::s_isWerehog); + // Instantly force the Player LED assignment + if (freeIndex != 0) // External controller + { + SDL_GameControllerSetPlayerIndex(controller.controller, freeIndex); // Set Player LED immediately + } + else + { + // Disable Player LED for Player 0 immediately + SDL_GameControllerSetPlayerIndex(controller.controller, -1); // Ensure Player 0 stays virtual + } - // Enforce Player 1's lightbar on the newly added controller instantly + // Immediately apply lightbar settings for Player 1 and sync with in-game logic + SetControllerTimeOfDayLED(g_controllers[0], App::s_isWerehog); SetControllerTimeOfDayLED(controller, App::s_isWerehog); + + // Forcefully override any lingering system defaults right after Lightbar reassignment + SDL_GameControllerSetLED( + controller.controller, + App::s_isWerehog ? 22 : 0, + App::s_isWerehog ? 0 : 37, + App::s_isWerehog ? 101 : 184 + ); } break; } @@ -240,7 +258,7 @@ int HID_OnSDLEvent(void*, SDL_Event* event) { controller->Close(); - // If Player 1's controller is removed, set the next available controller as active + // If Player 1's controller is removed, assign the next available controller as Player 1 if (controller == &g_controllers[0]) { for (auto& ctrl : g_controllers) @@ -250,12 +268,22 @@ int HID_OnSDLEvent(void*, SDL_Event* event) SetControllerInputDevice(&ctrl); g_controllers[0] = ctrl; - // Reapply the lightbar color to ensure custom in-game settings + // Instantly reapply Player 1's lightbar and Player LED settings SetControllerTimeOfDayLED(ctrl, App::s_isWerehog); + SDL_GameControllerSetPlayerIndex(ctrl.controller, 1); // Set as Player 1 break; } } } + + for (std::size_t i = 0; i < g_controllers.size(); ++i) + { + if (g_controllers[i].CanPoll()) + { + SDL_GameControllerSetPlayerIndex(g_controllers[i].controller, static_cast(i)); // Set Player LEDs + SetControllerTimeOfDayLED(g_controllers[i], App::s_isWerehog); // Sync lightbar + } + } } break; } From cebf5e6a1bb412b4f4350230253cd48c55e5d048 Mon Sep 17 00:00:00 2001 From: AL2009man <67606569+AL2009man@users.noreply.github.com> Date: Sat, 22 Mar 2025 02:04:43 -0400 Subject: [PATCH 14/15] remove code leftover this is to ensure there isn't any conflictions with other PRs --- UnleashedRecomp/hid/hid.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/UnleashedRecomp/hid/hid.h b/UnleashedRecomp/hid/hid.h index 28736f09..730694a9 100644 --- a/UnleashedRecomp/hid/hid.h +++ b/UnleashedRecomp/hid/hid.h @@ -7,8 +7,7 @@ namespace hid Keyboard, Mouse, Xbox, - PlayStation, - Unknown + PlayStation }; enum class EInputDeviceExplicit From de94e9004fc679ecdfc52aec6af485907e2240d5 Mon Sep 17 00:00:00 2001 From: AL2009man <67606569+AL2009man@users.noreply.github.com> Date: Mon, 24 Mar 2025 01:07:45 -0400 Subject: [PATCH 15/15] proper Daytime Lightbar and Player LED priority This adds a else portion for the IsNight Lightbar code. This is done by replicating isNight's dimmer lights and apply it to the Day portion for consistency. additionally: Player LED indicator should now play nicely when Player 0 (onboard inputs) and Player 1 (external) will respect it, although: you'll see the controller showing "Player 2" for a brief moment. --- UnleashedRecomp/hid/driver/sdl_hid.cpp | 133 ++++++++++--------------- 1 file changed, 53 insertions(+), 80 deletions(-) diff --git a/UnleashedRecomp/hid/driver/sdl_hid.cpp b/UnleashedRecomp/hid/driver/sdl_hid.cpp index f4c69483..8feb85d6 100644 --- a/UnleashedRecomp/hid/driver/sdl_hid.cpp +++ b/UnleashedRecomp/hid/driver/sdl_hid.cpp @@ -187,26 +187,28 @@ static void SetControllerInputDevice(Controller* controller) } } -static void SetControllerTimeOfDayLED(Controller& controller, bool isNight) +static void SetControllerTimeOfDayLED(Controller& ctrl, bool isNight) { - // Determine the lightbar color based on night of day. - auto r = isNight ? 22 : 0; - auto g = isNight ? 0 : 37; - auto b = isNight ? 101 : 184; + // Fetch the lightbar color based on the time of day + uint8_t r = 0, g = 0, b = 0; - // Set the LED for the given controller - controller.SetLED(r, g, b); + if (isNight) + { + r = 22; // Night: Red tone + g = 0; // Night: No green + b = 101; // Night: Cool blue + } + else + { + r = 0; // Day: No red + g = 0; // Day: No green + b = 60; // Day: Dimmer blue + } - // Ensure all other controllers mirror Player 1's lightbar - if (controller.controller == g_controllers[0].controller) // Check if it's Player 1 + // Apply the lightbar color to the specified controller + if (ctrl.CanPoll()) { - for (auto& ctrl : g_controllers) - { - if (ctrl.controller != g_controllers[0].controller) // Skip Player 1 itself - { - ctrl.SetLED(r, g, b); // Mirror Player 1's lightbar - } - } + ctrl.SetLED(r, g, b); } } @@ -224,28 +226,27 @@ int HID_OnSDLEvent(void*, SDL_Event* event) auto controller = Controller(event->cdevice.which); g_controllers[freeIndex] = controller; - // Instantly force the Player LED assignment - if (freeIndex != 0) // External controller + // Assign "Player 1 LED" for Player 0 and Player 1 + if (freeIndex == 0 || freeIndex == 1) // Player 0 and Player 1 both use "Player 1 LED" { - SDL_GameControllerSetPlayerIndex(controller.controller, freeIndex); // Set Player LED immediately + SDL_GameControllerSetPlayerIndex(controller.controller, 1); // Force Player LED to "Player 1" } - else + else // Additional external controllers (Player 2+) { - // Disable Player LED for Player 0 immediately - SDL_GameControllerSetPlayerIndex(controller.controller, -1); // Ensure Player 0 stays virtual + SDL_GameControllerSetPlayerIndex(controller.controller, freeIndex); // Assign correct LED for Player 2+ } - // Immediately apply lightbar settings for Player 1 and sync with in-game logic - SetControllerTimeOfDayLED(g_controllers[0], App::s_isWerehog); + // Apply lightbar settings immediately for the new controller SetControllerTimeOfDayLED(controller, App::s_isWerehog); - // Forcefully override any lingering system defaults right after Lightbar reassignment - SDL_GameControllerSetLED( - controller.controller, - App::s_isWerehog ? 22 : 0, - App::s_isWerehog ? 0 : 37, - App::s_isWerehog ? 101 : 184 - ); + // Refresh LEDs for all connected controllers + for (auto& ctrl : g_controllers) + { + if (ctrl.CanPoll()) + { + SDL_GameControllerSetPlayerIndex(ctrl.controller, ctrl.index); // Ensure correct LED updates + } + } } break; } @@ -258,30 +259,37 @@ int HID_OnSDLEvent(void*, SDL_Event* event) { controller->Close(); - // If Player 1's controller is removed, assign the next available controller as Player 1 - if (controller == &g_controllers[0]) + // If Player 1 disconnects, promote the next available controller to Player 1 + if (controller == &g_controllers[1]) // Player 1 removed { for (auto& ctrl : g_controllers) { - if (ctrl.CanPoll()) + if (ctrl.CanPoll() && &ctrl != &g_controllers[0]) // Skip Player 0 { - SetControllerInputDevice(&ctrl); - g_controllers[0] = ctrl; - - // Instantly reapply Player 1's lightbar and Player LED settings - SetControllerTimeOfDayLED(ctrl, App::s_isWerehog); - SDL_GameControllerSetPlayerIndex(ctrl.controller, 1); // Set as Player 1 + g_controllers[1] = ctrl; // Promote next available controller to Player 1 + SDL_GameControllerSetPlayerIndex(ctrl.controller, 1); // Reflect Player 1 LED + SetControllerTimeOfDayLED(ctrl, App::s_isWerehog); // Update lightbar break; } } } - for (std::size_t i = 0; i < g_controllers.size(); ++i) + // Update Player LED indices for all controllers immediately + for (std::size_t i = 2; i < g_controllers.size(); ++i) // Start from Player 2 onward { if (g_controllers[i].CanPoll()) { - SDL_GameControllerSetPlayerIndex(g_controllers[i].controller, static_cast(i)); // Set Player LEDs - SetControllerTimeOfDayLED(g_controllers[i], App::s_isWerehog); // Sync lightbar + SDL_GameControllerSetPlayerIndex(g_controllers[i].controller, static_cast(i)); // Assign correct LED index + SetControllerTimeOfDayLED(g_controllers[i], App::s_isWerehog); // Update lightbars + } + } + + // Refresh lightbar settings for all connected controllers + for (auto& ctrl : g_controllers) + { + if (ctrl.CanPoll()) + { + SetControllerTimeOfDayLED(ctrl, App::s_isWerehog); } } } @@ -298,6 +306,7 @@ int HID_OnSDLEvent(void*, SDL_Event* event) if (!controller) break; + // Process input if (event->type == SDL_CONTROLLERAXISMOTION) { if (abs(event->caxis.value) > 8000) @@ -316,46 +325,10 @@ int HID_OnSDLEvent(void*, SDL_Event* event) controller->Poll(); } - // Reapply the lightbar color to override system changes during input events + // Instantly apply updated lightbar settings during input SetControllerTimeOfDayLED(*controller, App::s_isWerehog); break; } - - case SDL_KEYDOWN: - case SDL_KEYUP: - hid::g_inputDevice = hid::EInputDevice::Keyboard; - break; - - case SDL_MOUSEMOTION: - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - { - if (!GameWindow::IsFullscreen() || GameWindow::s_isFullscreenCursorVisible) - SDL_ShowCursor(SDL_ENABLE); - - hid::g_inputDevice = hid::EInputDevice::Mouse; - break; - } - - case SDL_WINDOWEVENT: - { - if (event->window.event == SDL_WINDOWEVENT_FOCUS_LOST) - { - // Stop vibrating controllers on focus lost. - for (auto& controller : g_controllers) - controller.SetVibration({ 0, 0 }); - } - break; - } - - case SDL_USER_EVILSONIC: - { - // Refresh all controllers to ensure consistent lightbar colors - for (auto& controller : g_controllers) - SetControllerTimeOfDayLED(controller, event->user.code); - - break; - } } return 0;