using System.Runtime.InteropServices; using VRio.Core.Input; namespace VRio.App; /// /// Polls the first connected XInput (Xbox) controller and converts its state /// to the Core's normalized . Uses the in-box /// xinput1_4.dll (Windows 8+), falling back to xinput9_1_0.dll, so no /// redistributable is needed. Polling a disconnected user index is expensive, /// so while no pad is present the 0–3 scan only runs every ~1 s. /// internal sealed class XInputGamepad { private const int MaxUsers = 4; private const uint ErrorSuccess = 0; private const int ScanEveryNPolls = 60; // ≈1 s at the 16 ms input tick private static bool _tryXInput14 = true; private int _user = -1; private int _scanCountdown; /// True while a controller is connected and being polled. public bool Connected => _user >= 0; /// XInput user index of the connected controller (0-based). public int UserIndex => _user; /// /// Poll the pad. False (with a rest-state snapshot) while disconnected. /// public bool TryRead(out PadState state) { if (_user >= 0) { if (GetState(_user, out XINPUT_STATE s) == ErrorSuccess) { state = Convert(s.Gamepad); return true; } _user = -1; // unplugged; fall through to (throttled) rescan _scanCountdown = 0; } if (--_scanCountdown <= 0) { _scanCountdown = ScanEveryNPolls; for (int i = 0; i < MaxUsers; i++) { if (GetState(i, out XINPUT_STATE s) == ErrorSuccess) { _user = i; state = Convert(s.Gamepad); return true; } } } state = default; return false; } private static PadState Convert(in XINPUT_GAMEPAD g) => new( (PadButtons)g.wButtons, Thumb(g.sThumbLX), Thumb(g.sThumbLY), Thumb(g.sThumbRX), Thumb(g.sThumbRY), g.bLeftTrigger / 255f, g.bRightTrigger / 255f); private static float Thumb(short v) => Math.Max(-1f, v / 32767f); private static uint GetState(int user, out XINPUT_STATE state) { if (_tryXInput14) { try { return XInputGetState14(user, out state); } catch (DllNotFoundException) { _tryXInput14 = false; } } try { return XInputGetState910(user, out state); } catch (DllNotFoundException) { state = default; return uint.MaxValue; // no XInput on this system — behaves as "no pad" } } [DllImport("xinput1_4.dll", EntryPoint = "XInputGetState")] private static extern uint XInputGetState14(int dwUserIndex, out XINPUT_STATE state); [DllImport("xinput9_1_0.dll", EntryPoint = "XInputGetState")] private static extern uint XInputGetState910(int dwUserIndex, out XINPUT_STATE state); [StructLayout(LayoutKind.Sequential)] private struct XINPUT_STATE { public uint dwPacketNumber; public XINPUT_GAMEPAD Gamepad; } [StructLayout(LayoutKind.Sequential)] private struct XINPUT_GAMEPAD { public ushort wButtons; public byte bLeftTrigger; public byte bRightTrigger; public short sThumbLX; public short sThumbLY; public short sThumbRX; public short sThumbRY; } }