mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 22:07:05 +02:00
The launcher read no controller at all. Started from Steam (Desktop or Game Mode on a Steam Deck), Steam Input applies the game's gamepad layout to it, where the right trackpad is the right stick and its click is R3, so the trackpad did nothing; only the touch screen, which arrives as mouse messages, worked. - Every XInput pad is merged (Xbox, Steam Input's virtual pad), else a DualShock 4 / DualSense is read over HID with the reader the in-game menu already uses, now its own library (dingosdk_playstation_input). - D-pad and left stick move ImGui's focus, outlined in the launcher's blue; A / Cross presses. The hand-drawn tiles, switches and menu buttons take part in navigation (ImGuiButtonFlags_EnableNav). - B / Circle closes an open combo or menu and leaves a text field, and is Escape otherwise, which the launcher's pages already go back on. Escape now also closes the mod overviews and answers Cancel in the uninstall and replace confirmations; closing an overview no longer also leaves the page. - The right stick (the trackpad) moves a pointer that ImGui draws, and R3 (the trackpad's click) is the left button. The system cursor follows it; a mouse move or a touch elsewhere takes the pointer back. - The ImGui Win32 backend's own gamepad code (slot 0 only) is compiled out so it cannot fight the launcher's events; nothing else used it.
95 lines
3.9 KiB
C++
95 lines
3.9 KiB
C++
#include "gui_internal.h"
|
|
|
|
#include "Extension/UI/Overlay/playstation_input.h"
|
|
|
|
#include <Xinput.h>
|
|
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
namespace dingosdk::launcher_gui::detail {
|
|
namespace {
|
|
|
|
using GetState = DWORD(WINAPI*)(DWORD, XINPUT_STATE*);
|
|
// Loaded on first use like the overlay does: a PC without the XInput runtime
|
|
// still gets a launcher, just without Xbox pads.
|
|
GetState xinput_get_state() {
|
|
static const auto get_state = []() -> GetState {
|
|
for (const auto name : {L"xinput1_4.dll", L"xinput1_3.dll", L"xinput9_1_0.dll"}) {
|
|
const auto module = LoadLibraryExW(name, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
|
if (!module) continue;
|
|
if (const auto entry = GetProcAddress(module, "XInputGetState")) {
|
|
GetState function{};
|
|
static_assert(sizeof(function) == sizeof(entry));
|
|
std::memcpy(&function, &entry, sizeof(function));
|
|
return function;
|
|
}
|
|
FreeLibrary(module);
|
|
}
|
|
return nullptr;
|
|
}();
|
|
return get_state;
|
|
}
|
|
|
|
// Asking an empty XInput slot is slow (it looks for a device), so only slots
|
|
// with a pad are read every frame, and one empty slot is tried a second.
|
|
struct Slots {
|
|
unsigned connected{(1u << XUSER_MAX_COUNT) - 1}; // all four tried on the first frame
|
|
DWORD next_probe{};
|
|
ULONGLONG probe_at{};
|
|
};
|
|
|
|
} // namespace
|
|
|
|
PadState read_pad() {
|
|
static Slots slots;
|
|
PadState pad;
|
|
if (const auto get_state = xinput_get_state()) {
|
|
const auto now = GetTickCount64();
|
|
unsigned probe{};
|
|
if (now >= slots.probe_at) {
|
|
slots.probe_at = now + 1000;
|
|
for (DWORD tried = 0; tried < XUSER_MAX_COUNT && !probe; ++tried) {
|
|
const DWORD slot = (slots.next_probe + tried) % XUSER_MAX_COUNT;
|
|
if (!(slots.connected & (1u << slot))) probe = 1u << slot;
|
|
}
|
|
slots.next_probe = (slots.next_probe + 1) % XUSER_MAX_COUNT;
|
|
}
|
|
// Steam, DS4Windows and ViGEm put virtual pads in any slot, and an idle
|
|
// one may sit ahead of the pad in use, so every pad is merged.
|
|
for (DWORD slot = 0; slot < XUSER_MAX_COUNT; ++slot) {
|
|
const unsigned bit = 1u << slot;
|
|
if (!((slots.connected | probe) & bit)) continue;
|
|
XINPUT_STATE sample{};
|
|
if (get_state(slot, &sample) != ERROR_SUCCESS) { slots.connected &= ~bit; continue; }
|
|
slots.connected |= bit;
|
|
const auto& gamepad = sample.Gamepad;
|
|
pad.connected = true;
|
|
pad.buttons |= gamepad.wButtons;
|
|
pad.left_trigger = std::max(pad.left_trigger, gamepad.bLeftTrigger);
|
|
pad.right_trigger = std::max(pad.right_trigger, gamepad.bRightTrigger);
|
|
const auto magnitude = [](SHORT x, SHORT y) { return std::abs(int(x)) + std::abs(int(y)); };
|
|
if (magnitude(gamepad.sThumbLX, gamepad.sThumbLY) > magnitude(pad.left_x, pad.left_y)) {
|
|
pad.left_x = gamepad.sThumbLX; pad.left_y = gamepad.sThumbLY;
|
|
}
|
|
if (magnitude(gamepad.sThumbRX, gamepad.sThumbRY) > magnitude(pad.right_x, pad.right_y)) {
|
|
pad.right_x = gamepad.sThumbRX; pad.right_y = gamepad.sThumbRY;
|
|
}
|
|
}
|
|
}
|
|
// XInput wins whenever a pad is there: DS4Windows and Steam Input leave the
|
|
// PlayStation pad visible beside their XInput twin, and both would double up.
|
|
if (pad.connected) return pad;
|
|
const auto playstation = overlay::detail::read_playstation_pads();
|
|
if (!playstation.available) return {};
|
|
const auto& sony = playstation.pad;
|
|
pad.connected = true;
|
|
pad.buttons = sony.buttons;
|
|
pad.left_trigger = sony.left_trigger; pad.right_trigger = sony.right_trigger;
|
|
pad.left_x = sony.left_x; pad.left_y = sony.left_y;
|
|
pad.right_x = sony.right_x; pad.right_y = sony.right_y;
|
|
return pad;
|
|
}
|
|
|
|
} // namespace dingosdk::launcher_gui::detail
|