From f76b0cc51e1497b5ca5cbeb9ce8985ee0810a16d Mon Sep 17 00:00:00 2001 From: Wojciech Sobieszek Date: Fri, 13 Dec 2024 18:54:00 +0000 Subject: [PATCH 1/4] Add XInput for Windows --- src/DemoApp/Program.cs | 5 -- src/Joypad/ControllerEventArgs.cs | 6 ++ src/Joypad/ErrorEventArgs.cs | 6 -- src/Joypad/Joypad.csproj | 1 - src/Joypad/JoypadManager.cs | 64 ++++++-------- src/Joypad/Platforms/IDeviceManager.cs | 4 + .../Platforms/MacOS/HidDeviceManager.cs | 12 +-- .../Platforms/Windows/Interop/XInput.cs | 46 ++++++++++ .../Windows/Interop/XInputCapabilities.cs | 17 ++++ .../Windows/Interop/XInputGamepad.cs | 21 +++++ .../Platforms/Windows/Interop/XInputState.cs | 11 +++ .../Windows/Interop/XInputVibration.cs | 11 +++ .../Platforms/Windows/XInputController.cs | 84 +++++++++++++++++++ .../Platforms/Windows/XInputDeviceManager.cs | 65 ++++++++++++++ 14 files changed, 295 insertions(+), 58 deletions(-) create mode 100644 src/Joypad/ControllerEventArgs.cs delete mode 100644 src/Joypad/ErrorEventArgs.cs create mode 100644 src/Joypad/Platforms/Windows/Interop/XInput.cs create mode 100644 src/Joypad/Platforms/Windows/Interop/XInputCapabilities.cs create mode 100644 src/Joypad/Platforms/Windows/Interop/XInputGamepad.cs create mode 100644 src/Joypad/Platforms/Windows/Interop/XInputState.cs create mode 100644 src/Joypad/Platforms/Windows/Interop/XInputVibration.cs create mode 100644 src/Joypad/Platforms/Windows/XInputController.cs create mode 100644 src/Joypad/Platforms/Windows/XInputDeviceManager.cs diff --git a/src/DemoApp/Program.cs b/src/DemoApp/Program.cs index 4dc8ba1..114acfc 100644 --- a/src/DemoApp/Program.cs +++ b/src/DemoApp/Program.cs @@ -26,11 +26,6 @@ void ControllerOnValueChanged(object? sender, ControlEventArgs args) controller = null; }; -manager.ErrorOccurred += (_, e) => -{ - Console.WriteLine(e.Exception.ToString()); -}; - var cancellationToken = new CancellationTokenSource(); _ = Task.Factory.StartNew(() => { diff --git a/src/Joypad/ControllerEventArgs.cs b/src/Joypad/ControllerEventArgs.cs new file mode 100644 index 0000000..26276dd --- /dev/null +++ b/src/Joypad/ControllerEventArgs.cs @@ -0,0 +1,6 @@ +namespace OldBit.Joypad; + +internal class ControllerEventArgs(JoypadController controller) : EventArgs +{ + public JoypadController Controller { get; } = controller; +} diff --git a/src/Joypad/ErrorEventArgs.cs b/src/Joypad/ErrorEventArgs.cs deleted file mode 100644 index c26ef71..0000000 --- a/src/Joypad/ErrorEventArgs.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace OldBit.Joypad; - -public class ErrorEventArgs(Exception exception) : EventArgs -{ - public Exception Exception { get; } = exception; -} \ No newline at end of file diff --git a/src/Joypad/Joypad.csproj b/src/Joypad/Joypad.csproj index 80ddff6..a127f87 100644 --- a/src/Joypad/Joypad.csproj +++ b/src/Joypad/Joypad.csproj @@ -17,7 +17,6 @@ - diff --git a/src/Joypad/JoypadManager.cs b/src/Joypad/JoypadManager.cs index b66e0bb..8579009 100644 --- a/src/Joypad/JoypadManager.cs +++ b/src/Joypad/JoypadManager.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; -using System.Runtime.Versioning; using OldBit.Joypad.Platforms; using OldBit.Joypad.Platforms.MacOS; +using OldBit.Joypad.Platforms.Windows; namespace OldBit.Joypad; @@ -16,26 +16,49 @@ public sealed class JoypadManager : IDisposable public event EventHandler? ControllerConnected; public event EventHandler? ControllerDisconnected; - public event EventHandler? ErrorOccurred; public JoypadManager() { if (OperatingSystem.IsMacOS()) { - _deviceManager = CreateMacOSDeviceManager(); + _deviceManager = new HidDeviceManager(); } else if (OperatingSystem.IsWindows()) { - // TODO: Implement Windows device manager + _deviceManager = new XInputDeviceManager(); } else if (OperatingSystem.IsLinux()) { // TODO: Implement Linux device manager + throw new NotImplementedException(); } else { throw new PlatformNotSupportedException($"The {Environment.OSVersion.VersionString} platform is not supported."); } + + _deviceManager.ControllerAdded += (_, e) => + { + _controllers.Add(e.Controller); + + e.Controller.IsConnected = true; + ControllerConnected?.Invoke(this, new JoypadControllerEventArgs(e.Controller)); + }; + + _deviceManager.ControllerRemoved += (_, e) => + { + var existingController = Controllers.FirstOrDefault(c => c.Id == e.Controller.Id); + + if (existingController == null) + { + return; + } + + _controllers.Remove(existingController); + + e.Controller.IsConnected = false; + ControllerDisconnected?.Invoke(this, new JoypadControllerEventArgs(e.Controller)); + }; } public void Start() @@ -82,39 +105,6 @@ public bool TryGetController(Guid controllerId, [NotNullWhen(true)] out JoypadCo return controller != null; } - [SupportedOSPlatform("macos")] - private HidDeviceManager CreateMacOSDeviceManager() - { - var deviceManager = new HidDeviceManager(); - - deviceManager.ControllerAdded += (_, e) => - { - _controllers.Add(e.Controller); - - e.Controller.IsConnected = true; - ControllerConnected?.Invoke(this, new JoypadControllerEventArgs(e.Controller)); - }; - - deviceManager.ControllerRemoved += (_, e) => - { - var existingController = Controllers.FirstOrDefault(c => c.Id == e.Controller.Id); - - if (existingController == null) - { - return; - } - - _controllers.Remove(existingController); - - e.Controller.IsConnected = false; - ControllerDisconnected?.Invoke(this, new JoypadControllerEventArgs(e.Controller)); - }; - - deviceManager.ErrorOccurred += (sender, e) => ErrorOccurred?.Invoke(sender, e); - - return deviceManager; - } - public void Dispose() { _deviceManager?.Dispose(); diff --git a/src/Joypad/Platforms/IDeviceManager.cs b/src/Joypad/Platforms/IDeviceManager.cs index a0c04a6..a4c584d 100644 --- a/src/Joypad/Platforms/IDeviceManager.cs +++ b/src/Joypad/Platforms/IDeviceManager.cs @@ -2,6 +2,10 @@ namespace OldBit.Joypad.Platforms; internal interface IDeviceManager : IDisposable { + event EventHandler? ControllerAdded; + + event EventHandler? ControllerRemoved; + void StartListener(); void StopListener(); diff --git a/src/Joypad/Platforms/MacOS/HidDeviceManager.cs b/src/Joypad/Platforms/MacOS/HidDeviceManager.cs index c841551..753b414 100644 --- a/src/Joypad/Platforms/MacOS/HidDeviceManager.cs +++ b/src/Joypad/Platforms/MacOS/HidDeviceManager.cs @@ -6,11 +6,6 @@ namespace OldBit.Joypad.Platforms.MacOS; -internal class ControllerEventArgs(HidController controller) : EventArgs -{ - public HidController Controller { get; } = controller; -} - [SupportedOSPlatform("macos")] internal class HidDeviceManager : IDeviceManager { @@ -19,9 +14,8 @@ internal class HidDeviceManager : IDeviceManager private GCHandle _gch; private readonly Thread _runLoopThread; - internal event EventHandler? ControllerAdded; - internal event EventHandler? ControllerRemoved; - internal event EventHandler? ErrorOccurred; + public event EventHandler? ControllerAdded; + public event EventHandler? ControllerRemoved; internal HidDeviceManager() { @@ -65,7 +59,7 @@ private void RunLoopRunThread() } catch (Exception ex) { - ErrorOccurred?.Invoke(this, new ErrorEventArgs(ex)); + // TODO: Log exception } } diff --git a/src/Joypad/Platforms/Windows/Interop/XInput.cs b/src/Joypad/Platforms/Windows/Interop/XInput.cs new file mode 100644 index 0000000..2270f83 --- /dev/null +++ b/src/Joypad/Platforms/Windows/Interop/XInput.cs @@ -0,0 +1,46 @@ +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace OldBit.Joypad.Platforms.Windows.Interop; + +[SupportedOSPlatform("windows")] +internal static partial class XInput +{ + internal const int ErrorSuccess = 0x0000; + internal const int ErrorDeviceNotConnected = 0x048F; + + internal const int XInputDevTypeGamepad = 0x0001; + + internal const int XInputDevSubTypeUnknown = 0x00; + internal const int XInputDevSubTypeGamepad = 0x01; + internal const int XInputDevSubTypeWheel = 0x02; + internal const int XInputDevSubTypeArcadeStick = 0x03; + internal const int XInputDevSubTypeFlightStick = 0x04; + internal const int XInputDevSubTypeDancePad = 0x05; + internal const int XInputDevSubTypeGuitar = 0x06; + internal const int XInputDevSubTypeGuitarAlternate = 0x07; + internal const int XInputDevSubTypeDrumKit = 0x08; + internal const int XInputDevSubTypeGuitarBass = 0x0B; + internal const int XInputDevSubTypeArcadePad = 0x13; + + internal const int XInputGamepadDPadUp = 0x0001; + internal const int XInputGamepadDPadDown = 0x0002; + internal const int XInputGamepadDPadLeft = 0x0004; + internal const int XInputGamepadDPadRight = 0x0008; + internal const int XInputGamepadStart = 0x0010; + internal const int XInputGamepadBack = 0x0020; + internal const int XInputGamepadLeftThumb = 0x0040; + internal const int XInputGamepadRightThumb = 0x0080; + internal const int XInputGamepadLeftShoulder = 0x0100; + internal const int XInputGamepadRightShoulder = 0x0200; + internal const int XInputGamepadA = 0x1000; + internal const int XInputGamepadB = 0x2000; + internal const int XInputGamepadX = 0x4000; + internal const int XInputGamepadY = 0x8000; + + [LibraryImport("Xinput1_4.dll")] + internal static partial int XInputGetState(int userIndex, out XInputState state); + + [LibraryImport("Xinput1_4.dll")] + internal static partial int XInputGetCapabilities(int userIndex, int flags, out XInputCapabilities capabilities); +} diff --git a/src/Joypad/Platforms/Windows/Interop/XInputCapabilities.cs b/src/Joypad/Platforms/Windows/Interop/XInputCapabilities.cs new file mode 100644 index 0000000..abd5474 --- /dev/null +++ b/src/Joypad/Platforms/Windows/Interop/XInputCapabilities.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace OldBit.Joypad.Platforms.Windows.Interop; + +[StructLayout(LayoutKind.Sequential)] +internal struct XInputCapabilities +{ + internal byte Type; + + internal byte SubType; + + internal ushort Flags; + + internal XInputGamepad Gamepad; + + internal XInputVibration Vibration; +} diff --git a/src/Joypad/Platforms/Windows/Interop/XInputGamepad.cs b/src/Joypad/Platforms/Windows/Interop/XInputGamepad.cs new file mode 100644 index 0000000..8c3feb9 --- /dev/null +++ b/src/Joypad/Platforms/Windows/Interop/XInputGamepad.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace OldBit.Joypad.Platforms.Windows.Interop; + +[StructLayout(LayoutKind.Sequential)] +internal struct XInputGamepad +{ + internal ushort Buttons; + + internal byte LeftTrigger; + + internal byte RightTrigger; + + internal short ThumbLX; + + internal short ThumbLY; + + internal short ThumbRX; + + internal short ThumbRY; +} diff --git a/src/Joypad/Platforms/Windows/Interop/XInputState.cs b/src/Joypad/Platforms/Windows/Interop/XInputState.cs new file mode 100644 index 0000000..1c02f8f --- /dev/null +++ b/src/Joypad/Platforms/Windows/Interop/XInputState.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace OldBit.Joypad.Platforms.Windows.Interop; + +[StructLayout(LayoutKind.Sequential)] +internal struct XInputState +{ + internal uint PacketNumber; + + internal XInputGamepad Gamepad; +} diff --git a/src/Joypad/Platforms/Windows/Interop/XInputVibration.cs b/src/Joypad/Platforms/Windows/Interop/XInputVibration.cs new file mode 100644 index 0000000..47889aa --- /dev/null +++ b/src/Joypad/Platforms/Windows/Interop/XInputVibration.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace OldBit.Joypad.Platforms.Windows.Interop; + +[StructLayout(LayoutKind.Sequential)] +internal struct XInputVibration +{ + internal ushort LeftMotorSpeed; + + internal ushort RightMotorSpeed; +} diff --git a/src/Joypad/Platforms/Windows/XInputController.cs b/src/Joypad/Platforms/Windows/XInputController.cs new file mode 100644 index 0000000..81214c2 --- /dev/null +++ b/src/Joypad/Platforms/Windows/XInputController.cs @@ -0,0 +1,84 @@ +using OldBit.Joypad.Controls; +using OldBit.Joypad.Platforms.Windows.Interop; +using System.Runtime.Versioning; +using static OldBit.Joypad.Platforms.Windows.Interop.XInput; + +namespace OldBit.Joypad.Platforms.Windows; + +[SupportedOSPlatform("windows")] +internal class XInputController : JoypadController +{ + private readonly int _controllerIndex; + private readonly XInputCapabilities _capabilities; + + internal XInputController(int controllerIndex, XInputCapabilities capabilities) + { + _controllerIndex = controllerIndex; + _capabilities = capabilities; + + AddControls(capabilities); + + Name = CreateName(); + Id = CreateDeviceUniqueId(); + } + + protected override int? GetValue(Control control) + { + var state = GetState(_controllerIndex); + + if (state == null) + { + return null; + } + + return null; + } + + private void AddControls(XInputCapabilities capabilities) + { + + } + + private string CreateName() + { + var name = _capabilities.SubType switch + { + XInputDevSubTypeGamepad => "Gamepad", + XInputDevSubTypeWheel => "Wheel", + XInputDevSubTypeArcadeStick => "Arcade Stick", + XInputDevSubTypeFlightStick => "Flight Stick", + XInputDevSubTypeDancePad => "Dance Pad", + XInputDevSubTypeGuitar => "Guitar", + XInputDevSubTypeGuitarAlternate => "Guitar (Alternate)", + XInputDevSubTypeDrumKit => "Drum Kit", + XInputDevSubTypeGuitarBass => "Guitar Bass", + XInputDevSubTypeArcadePad => "Arcade Pad", + _ => "Unknown" + }; + + return $"{name} {_controllerIndex + 1}"; + } + + private Guid CreateDeviceUniqueId() + { + return new Guid( + _capabilities.Gamepad.Buttons, + (short)(_controllerIndex + 1), + 3, + _capabilities.Type, + _capabilities.SubType, + 6, + 7, + 8, + 9, + _capabilities.Gamepad.LeftTrigger, + _capabilities.Gamepad.RightTrigger); + } + + private static XInputGamepad? GetState(int userIndex) + { + var result = XInputGetState(userIndex, out var state); + + return result == ErrorSuccess ? state.Gamepad : null; + } +} diff --git a/src/Joypad/Platforms/Windows/XInputDeviceManager.cs b/src/Joypad/Platforms/Windows/XInputDeviceManager.cs new file mode 100644 index 0000000..857157c --- /dev/null +++ b/src/Joypad/Platforms/Windows/XInputDeviceManager.cs @@ -0,0 +1,65 @@ +using OldBit.Joypad.Platforms.Windows.Interop; +using System.Runtime.Versioning; +using System.Timers; +using Timer = System.Timers.Timer; +using static OldBit.Joypad.Platforms.Windows.Interop.XInput; + +namespace OldBit.Joypad.Platforms.Windows; + +[SupportedOSPlatform("windows")] +internal class XInputDeviceManager : IDeviceManager +{ + private const int MaxControllers = 4; + + private readonly TimeSpan _pollingInterval = TimeSpan.FromSeconds(4); + private readonly Timer _devicePollingTimer; + private readonly XInputController?[] _controllers = new XInputController[MaxControllers]; + + public event EventHandler? ControllerAdded; + public event EventHandler? ControllerRemoved; + + public XInputDeviceManager() + { + _devicePollingTimer = new Timer(_pollingInterval) + { + AutoReset = false + }; + + _devicePollingTimer.Elapsed += CheckControllers; + } + + public void StartListener() => _devicePollingTimer.Start(); + + public void StopListener() => _devicePollingTimer.Stop(); + + private void CheckControllers(object? sender, ElapsedEventArgs e) + { + for (var userIndex = 0; userIndex < MaxControllers; userIndex++) + { + var capabilities = GetCapabilities(userIndex); + + if (capabilities == null && _controllers[userIndex] != null) + { + ControllerRemoved?.Invoke(this, new ControllerEventArgs(_controllers[userIndex]!)); + _controllers[userIndex] = null; + } + + if (capabilities != null && _controllers[userIndex] == null) + { + var controller = new XInputController(userIndex, capabilities.Value); + _controllers[userIndex] = controller; + + ControllerAdded?.Invoke(this, new ControllerEventArgs(controller)); + } + } + } + + private static XInputCapabilities? GetCapabilities(int userIndex) + { + var result = XInputGetCapabilities(userIndex, XInputDevTypeGamepad, out var capabilities); + + return result == ErrorSuccess ? capabilities : null; + } + + public void Dispose() => _devicePollingTimer.Dispose(); +} From 67ddf271e50c575f59aa16e50f43cd0b197f707c Mon Sep 17 00:00:00 2001 From: voytas Date: Fri, 13 Dec 2024 20:25:49 +0000 Subject: [PATCH 2/4] XInput work in progress --- src/Joypad/Controls/Control.cs | 2 +- src/Joypad/JoypadController.cs | 14 +- src/Joypad/JoypadManager.cs | 5 +- .../Windows/Interop/XInputCapabilities.cs | 8 +- .../Platforms/Windows/Interop/XInputState.cs | 2 +- .../Windows/Interop/XInputVibration.cs | 2 +- src/Joypad/Platforms/Windows/XInputControl.cs | 25 ++++ .../Platforms/Windows/XInputController.cs | 137 ++++++++++++++++-- .../Platforms/Windows/XInputDeviceManager.cs | 6 +- 9 files changed, 175 insertions(+), 26 deletions(-) create mode 100644 src/Joypad/Platforms/Windows/XInputControl.cs diff --git a/src/Joypad/Controls/Control.cs b/src/Joypad/Controls/Control.cs index 402897c..ca2a094 100644 --- a/src/Joypad/Controls/Control.cs +++ b/src/Joypad/Controls/Control.cs @@ -4,7 +4,7 @@ public abstract class Control(ControlType controlType) { public ControlType ControlType { get; } = controlType; - public int Id { get; protected set; } + public int Id { get; protected init; } public string Name { get; protected init; } = string.Empty; diff --git a/src/Joypad/JoypadController.cs b/src/Joypad/JoypadController.cs index 2d0c16b..34b3a31 100644 --- a/src/Joypad/JoypadController.cs +++ b/src/Joypad/JoypadController.cs @@ -37,7 +37,17 @@ internal JoypadController() { } - internal void Update(Control control) + internal void Update() + { + UpdateState(); + + foreach (var control in Controls) + { + ProcessControl(control); + } + } + + private void ProcessControl(Control control) { if (!IsConnected) { @@ -67,6 +77,8 @@ internal void Initialize() } } + protected virtual void UpdateState() { } + protected abstract int? GetValue(Control control); internal void AddControl(Control control) diff --git a/src/Joypad/JoypadManager.cs b/src/Joypad/JoypadManager.cs index 8579009..3be3613 100644 --- a/src/Joypad/JoypadManager.cs +++ b/src/Joypad/JoypadManager.cs @@ -92,10 +92,7 @@ public void Update(Guid controllerId) return; } - foreach (var control in controller.Controls) - { - controller.Update(control); - } + controller.Update(); } public bool TryGetController(Guid controllerId, [NotNullWhen(true)] out JoypadController? controller) diff --git a/src/Joypad/Platforms/Windows/Interop/XInputCapabilities.cs b/src/Joypad/Platforms/Windows/Interop/XInputCapabilities.cs index abd5474..d863a99 100644 --- a/src/Joypad/Platforms/Windows/Interop/XInputCapabilities.cs +++ b/src/Joypad/Platforms/Windows/Interop/XInputCapabilities.cs @@ -6,12 +6,12 @@ namespace OldBit.Joypad.Platforms.Windows.Interop; internal struct XInputCapabilities { internal byte Type; - + internal byte SubType; - + internal ushort Flags; - + internal XInputGamepad Gamepad; - + internal XInputVibration Vibration; } diff --git a/src/Joypad/Platforms/Windows/Interop/XInputState.cs b/src/Joypad/Platforms/Windows/Interop/XInputState.cs index 1c02f8f..a0db610 100644 --- a/src/Joypad/Platforms/Windows/Interop/XInputState.cs +++ b/src/Joypad/Platforms/Windows/Interop/XInputState.cs @@ -6,6 +6,6 @@ namespace OldBit.Joypad.Platforms.Windows.Interop; internal struct XInputState { internal uint PacketNumber; - + internal XInputGamepad Gamepad; } diff --git a/src/Joypad/Platforms/Windows/Interop/XInputVibration.cs b/src/Joypad/Platforms/Windows/Interop/XInputVibration.cs index 47889aa..b9ae046 100644 --- a/src/Joypad/Platforms/Windows/Interop/XInputVibration.cs +++ b/src/Joypad/Platforms/Windows/Interop/XInputVibration.cs @@ -6,6 +6,6 @@ namespace OldBit.Joypad.Platforms.Windows.Interop; internal struct XInputVibration { internal ushort LeftMotorSpeed; - + internal ushort RightMotorSpeed; } diff --git a/src/Joypad/Platforms/Windows/XInputControl.cs b/src/Joypad/Platforms/Windows/XInputControl.cs new file mode 100644 index 0000000..f1eed2b --- /dev/null +++ b/src/Joypad/Platforms/Windows/XInputControl.cs @@ -0,0 +1,25 @@ +using System.Runtime.Versioning; +using OldBit.Joypad.Controls; + +namespace OldBit.Joypad.Platforms.Windows; + +[SupportedOSPlatform("windows")] +internal class XInputControl : Control +{ + private DirectionalPadDirection _direction; + + private XInputControl(ControlType controlType, int inputId, string name, DirectionalPadDirection direction = DirectionalPadDirection.None) : base(controlType) + { + Name = name; + _direction = direction; + + Id = inputId; + } + + internal static XInputControl CreateButton(int inputId, string name, DirectionalPadDirection direction = DirectionalPadDirection.None) => + new(ControlType.Button, inputId, name, direction); + + + internal static XInputControl CreateThumbStick(int inputId, string name) => + new(ControlType.ThumbStick, inputId, name); +} \ No newline at end of file diff --git a/src/Joypad/Platforms/Windows/XInputController.cs b/src/Joypad/Platforms/Windows/XInputController.cs index 81214c2..7114808 100644 --- a/src/Joypad/Platforms/Windows/XInputController.cs +++ b/src/Joypad/Platforms/Windows/XInputController.cs @@ -10,12 +10,15 @@ internal class XInputController : JoypadController { private readonly int _controllerIndex; private readonly XInputCapabilities _capabilities; + private readonly HashSet _controls = []; + + private XInputGamepad? _state = null; internal XInputController(int controllerIndex, XInputCapabilities capabilities) { _controllerIndex = controllerIndex; _capabilities = capabilities; - + AddControls(capabilities); Name = CreateName(); @@ -24,19 +27,128 @@ internal XInputController(int controllerIndex, XInputCapabilities capabilities) protected override int? GetValue(Control control) { - var state = GetState(_controllerIndex); - - if (state == null) + if (_state == null) { return null; } + // Retrieve the value of the control from the current state + return null; } + protected override void UpdateState() => + _state = GetState(_controllerIndex); + private void AddControls(XInputCapabilities capabilities) { + if (HasButton(capabilities.Gamepad, XInputGamepadDPadUp)) + { + var control = XInputControl.CreateButton(XInputGamepadDPadUp, "Directional Pad", DirectionalPadDirection.Up); + _controls.Add(control); + } + + if (HasButton(capabilities.Gamepad, XInputGamepadDPadDown)) + { + var control = XInputControl.CreateButton(XInputGamepadDPadDown, "Directional Pad", DirectionalPadDirection.Down); + _controls.Add(control); + } + + if (HasButton(capabilities.Gamepad, XInputGamepadDPadLeft)) + { + var control = XInputControl.CreateButton(XInputGamepadDPadLeft, "Directional Pad", DirectionalPadDirection.Left); + _controls.Add(control); + } + + if (HasButton(capabilities.Gamepad, XInputGamepadDPadRight)) + { + var control = XInputControl.CreateButton(XInputGamepadDPadRight, "Directional Pad", DirectionalPadDirection.Right); + _controls.Add(control); + } + + if (HasButton(capabilities.Gamepad, XInputGamepadStart)) + { + var control = XInputControl.CreateButton(XInputGamepadStart, "Start"); + _controls.Add(control); + } + + if (HasButton(capabilities.Gamepad, XInputGamepadBack)) + { + var control = XInputControl.CreateButton(XInputGamepadBack, "Back"); + _controls.Add(control); + } + + if (HasButton(capabilities.Gamepad, XInputGamepadLeftThumb)) + { + var control = XInputControl.CreateButton(XInputGamepadLeftThumb, "Left Thumb"); + _controls.Add(control); + } + + if (HasButton(capabilities.Gamepad, XInputGamepadRightThumb)) + { + var control = XInputControl.CreateButton(XInputGamepadRightThumb, "Right Thumb"); + _controls.Add(control); + } + + if (HasButton(capabilities.Gamepad, XInputGamepadLeftShoulder)) + { + var control = XInputControl.CreateButton(XInputGamepadLeftShoulder, "Left Shoulder"); + _controls.Add(control); + } + + if (HasButton(capabilities.Gamepad, XInputGamepadRightShoulder)) + { + var control = XInputControl.CreateButton(XInputGamepadRightShoulder, "Right Shoulder"); + _controls.Add(control); + } + + if (HasButton(capabilities.Gamepad, XInputGamepadA)) + { + var control = XInputControl.CreateButton(XInputGamepadA, "A"); + _controls.Add(control); + } + if (HasButton(capabilities.Gamepad, XInputGamepadB)) + { + var control = XInputControl.CreateButton(XInputGamepadB, "B"); + _controls.Add(control); + } + + if (HasButton(capabilities.Gamepad, XInputGamepadX)) + { + var control = XInputControl.CreateButton(XInputGamepadX, "X"); + _controls.Add(control); + } + + if (HasButton(capabilities.Gamepad, XInputGamepadY)) + { + var control = XInputControl.CreateButton(XInputGamepadY, "Y"); + _controls.Add(control); + } + + if (capabilities.Gamepad.ThumbLX != 0) + { + var control = XInputControl.CreateThumbStick(0x800001, "Left Thumb X"); + _controls.Add(control); + } + + if (capabilities.Gamepad.ThumbLY != 0) + { + var control = XInputControl.CreateThumbStick(0x800002, "Left Thumb Y"); + _controls.Add(control); + } + + if (capabilities.Gamepad.ThumbRX != 0) + { + var control = XInputControl.CreateThumbStick(0x800003, "Right Thumb X"); + _controls.Add(control); + } + + if (capabilities.Gamepad.ThumbRY != 0) + { + var control = XInputControl.CreateThumbStick(0x800004, "Right Thumb Y"); + _controls.Add(control); + } } private string CreateName() @@ -64,13 +176,13 @@ private Guid CreateDeviceUniqueId() return new Guid( _capabilities.Gamepad.Buttons, (short)(_controllerIndex + 1), - 3, - _capabilities.Type, - _capabilities.SubType, - 6, - 7, - 8, - 9, + 3, + _capabilities.Type, + _capabilities.SubType, + 6, + 7, + 8, + 9, _capabilities.Gamepad.LeftTrigger, _capabilities.Gamepad.RightTrigger); } @@ -81,4 +193,7 @@ private Guid CreateDeviceUniqueId() return result == ErrorSuccess ? state.Gamepad : null; } + + private static bool HasButton(XInputGamepad gamepad, int button) => + (gamepad.Buttons & button) == button; } diff --git a/src/Joypad/Platforms/Windows/XInputDeviceManager.cs b/src/Joypad/Platforms/Windows/XInputDeviceManager.cs index 857157c..8bd8e66 100644 --- a/src/Joypad/Platforms/Windows/XInputDeviceManager.cs +++ b/src/Joypad/Platforms/Windows/XInputDeviceManager.cs @@ -10,7 +10,7 @@ namespace OldBit.Joypad.Platforms.Windows; internal class XInputDeviceManager : IDeviceManager { private const int MaxControllers = 4; - + private readonly TimeSpan _pollingInterval = TimeSpan.FromSeconds(4); private readonly Timer _devicePollingTimer; private readonly XInputController?[] _controllers = new XInputController[MaxControllers]; @@ -22,7 +22,7 @@ public XInputDeviceManager() { _devicePollingTimer = new Timer(_pollingInterval) { - AutoReset = false + AutoReset = true }; _devicePollingTimer.Elapsed += CheckControllers; @@ -48,7 +48,7 @@ private void CheckControllers(object? sender, ElapsedEventArgs e) { var controller = new XInputController(userIndex, capabilities.Value); _controllers[userIndex] = controller; - + ControllerAdded?.Invoke(this, new ControllerEventArgs(controller)); } } From 17f92d9c4f86774396c8d374d7349d0acd8d5f53 Mon Sep 17 00:00:00 2001 From: Wojciech Sobieszek Date: Fri, 13 Dec 2024 23:55:35 +0000 Subject: [PATCH 3/4] Get control value --- src/Joypad/Platforms/Windows/XInputControl.cs | 12 +- .../Platforms/Windows/XInputController.cs | 123 +++++++++++++----- 2 files changed, 97 insertions(+), 38 deletions(-) diff --git a/src/Joypad/Platforms/Windows/XInputControl.cs b/src/Joypad/Platforms/Windows/XInputControl.cs index f1eed2b..d820990 100644 --- a/src/Joypad/Platforms/Windows/XInputControl.cs +++ b/src/Joypad/Platforms/Windows/XInputControl.cs @@ -11,15 +11,17 @@ internal class XInputControl : Control private XInputControl(ControlType controlType, int inputId, string name, DirectionalPadDirection direction = DirectionalPadDirection.None) : base(controlType) { Name = name; - _direction = direction; - Id = inputId; - } - internal static XInputControl CreateButton(int inputId, string name, DirectionalPadDirection direction = DirectionalPadDirection.None) => - new(ControlType.Button, inputId, name, direction); + _direction = direction; + } + internal static XInputControl CreateButton(int inputId, string name) => + new(ControlType.Button, inputId, name); internal static XInputControl CreateThumbStick(int inputId, string name) => new(ControlType.ThumbStick, inputId, name); + + internal static XInputControl CreateDirectionalPad(int inputId, string name, DirectionalPadDirection direction) => + new(ControlType.ThumbStick, inputId, name, direction); } \ No newline at end of file diff --git a/src/Joypad/Platforms/Windows/XInputController.cs b/src/Joypad/Platforms/Windows/XInputController.cs index 7114808..b81c4c3 100644 --- a/src/Joypad/Platforms/Windows/XInputController.cs +++ b/src/Joypad/Platforms/Windows/XInputController.cs @@ -1,6 +1,6 @@ -using OldBit.Joypad.Controls; +using System.Runtime.Versioning; +using OldBit.Joypad.Controls; using OldBit.Joypad.Platforms.Windows.Interop; -using System.Runtime.Versioning; using static OldBit.Joypad.Platforms.Windows.Interop.XInput; namespace OldBit.Joypad.Platforms.Windows; @@ -8,9 +8,15 @@ namespace OldBit.Joypad.Platforms.Windows; [SupportedOSPlatform("windows")] internal class XInputController : JoypadController { + private const int LeftThumbX = 0x800001; + private const int LeftThumbY = 0x800002; + private const int RightThumbX = 0x800003; + private const int RightThumbY = 0x800004; + private const int LeftTrigger = 0x800005; + private const int RightTrigger = 0x800006; + private readonly int _controllerIndex; private readonly XInputCapabilities _capabilities; - private readonly HashSet _controls = []; private XInputGamepad? _state = null; @@ -32,122 +38,170 @@ internal XInputController(int controllerIndex, XInputCapabilities capabilities) return null; } - // Retrieve the value of the control from the current state + switch (control.Id) + { + case XInputGamepadDPadUp: + case XInputGamepadDPadDown: + case XInputGamepadDPadLeft: + case XInputGamepadDPadRight: + case XInputGamepadStart: + case XInputGamepadBack: + case XInputGamepadLeftThumb: + case XInputGamepadRightThumb: + case XInputGamepadLeftShoulder: + case XInputGamepadRightShoulder: + case XInputGamepadA: + case XInputGamepadB: + case XInputGamepadX: + case XInputGamepadY: + return GetButtonValue(XInputGamepadDPadUp); + + case LeftThumbX: + return _state.Value.ThumbLX; + + case LeftThumbY: + return _state.Value.ThumbLY; + + case RightThumbX: + return _state.Value.ThumbRX; + + case RightThumbY: + return _state.Value.ThumbRY; + + case LeftTrigger: + return _state.Value.LeftTrigger; + + case RightTrigger: + return _state.Value.RightTrigger; + + default: + return null; - return null; + } } - protected override void UpdateState() => - _state = GetState(_controllerIndex); + protected override void UpdateState() => _state = GetState(_controllerIndex); private void AddControls(XInputCapabilities capabilities) { if (HasButton(capabilities.Gamepad, XInputGamepadDPadUp)) { - var control = XInputControl.CreateButton(XInputGamepadDPadUp, "Directional Pad", DirectionalPadDirection.Up); - _controls.Add(control); + var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadUp, "Directional Pad", DirectionalPadDirection.Up); + AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadDPadDown)) { - var control = XInputControl.CreateButton(XInputGamepadDPadDown, "Directional Pad", DirectionalPadDirection.Down); - _controls.Add(control); + var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadDown, "Directional Pad", DirectionalPadDirection.Down); + AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadDPadLeft)) { - var control = XInputControl.CreateButton(XInputGamepadDPadLeft, "Directional Pad", DirectionalPadDirection.Left); - _controls.Add(control); + var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadLeft, "Directional Pad", DirectionalPadDirection.Left); + AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadDPadRight)) { - var control = XInputControl.CreateButton(XInputGamepadDPadRight, "Directional Pad", DirectionalPadDirection.Right); - _controls.Add(control); + var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadRight, "Directional Pad", DirectionalPadDirection.Right); + AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadStart)) { var control = XInputControl.CreateButton(XInputGamepadStart, "Start"); - _controls.Add(control); + AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadBack)) { var control = XInputControl.CreateButton(XInputGamepadBack, "Back"); - _controls.Add(control); + AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadLeftThumb)) { var control = XInputControl.CreateButton(XInputGamepadLeftThumb, "Left Thumb"); - _controls.Add(control); + AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadRightThumb)) { var control = XInputControl.CreateButton(XInputGamepadRightThumb, "Right Thumb"); - _controls.Add(control); + AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadLeftShoulder)) { var control = XInputControl.CreateButton(XInputGamepadLeftShoulder, "Left Shoulder"); - _controls.Add(control); + AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadRightShoulder)) { var control = XInputControl.CreateButton(XInputGamepadRightShoulder, "Right Shoulder"); - _controls.Add(control); + AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadA)) { var control = XInputControl.CreateButton(XInputGamepadA, "A"); - _controls.Add(control); + AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadB)) { var control = XInputControl.CreateButton(XInputGamepadB, "B"); - _controls.Add(control); + AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadX)) { var control = XInputControl.CreateButton(XInputGamepadX, "X"); - _controls.Add(control); + AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadY)) { var control = XInputControl.CreateButton(XInputGamepadY, "Y"); - _controls.Add(control); + AddControl(control); } if (capabilities.Gamepad.ThumbLX != 0) { - var control = XInputControl.CreateThumbStick(0x800001, "Left Thumb X"); - _controls.Add(control); + var control = XInputControl.CreateThumbStick(LeftThumbX, "Left Thumb X"); + AddControl(control); } if (capabilities.Gamepad.ThumbLY != 0) { - var control = XInputControl.CreateThumbStick(0x800002, "Left Thumb Y"); - _controls.Add(control); + var control = XInputControl.CreateThumbStick(LeftThumbY, "Left Thumb Y"); + AddControl(control); } if (capabilities.Gamepad.ThumbRX != 0) { - var control = XInputControl.CreateThumbStick(0x800003, "Right Thumb X"); - _controls.Add(control); + var control = XInputControl.CreateThumbStick(RightThumbX, "Right Thumb X"); + AddControl(control); } if (capabilities.Gamepad.ThumbRY != 0) { - var control = XInputControl.CreateThumbStick(0x800004, "Right Thumb Y"); - _controls.Add(control); + var control = XInputControl.CreateThumbStick(RightThumbY, "Right Thumb Y"); + AddControl(control); + } + + if (capabilities.Gamepad.LeftTrigger != 0) + { + var control = XInputControl.CreateButton(LeftTrigger, "Left Trigger"); + AddControl(control); + } + + if (capabilities.Gamepad.RightTrigger != 0) + { + var control = XInputControl.CreateButton(RightTrigger, "Right Trigger"); + AddControl(control); } } @@ -196,4 +250,7 @@ private Guid CreateDeviceUniqueId() private static bool HasButton(XInputGamepad gamepad, int button) => (gamepad.Buttons & button) == button; + + private int GetButtonValue(int controlId) => + ((_state?.Buttons ?? 0) & controlId) != 0 ? 1 : 0; } From 12ce35755106b2cd548f437fe3600a042c8da6fb Mon Sep 17 00:00:00 2001 From: voytas Date: Mon, 16 Dec 2024 22:04:14 +0000 Subject: [PATCH 4/4] Test on windows --- src/Joypad/JoypadController.cs | 2 ++ src/Joypad/JoypadManager.cs | 3 ++- src/Joypad/Platforms/Windows/XInputController.cs | 13 ++++++------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Joypad/JoypadController.cs b/src/Joypad/JoypadController.cs index 34b3a31..54eb829 100644 --- a/src/Joypad/JoypadController.cs +++ b/src/Joypad/JoypadController.cs @@ -71,6 +71,8 @@ public bool TryGetControl(int controlId, [NotNullWhen(true)] out Control? contro internal void Initialize() { + UpdateState(); + foreach (var control in _controls) { control.Value = GetValue(control); diff --git a/src/Joypad/JoypadManager.cs b/src/Joypad/JoypadManager.cs index 3be3613..513d900 100644 --- a/src/Joypad/JoypadManager.cs +++ b/src/Joypad/JoypadManager.cs @@ -40,8 +40,9 @@ public JoypadManager() _deviceManager.ControllerAdded += (_, e) => { _controllers.Add(e.Controller); - e.Controller.IsConnected = true; + e.Controller.Initialize(); + ControllerConnected?.Invoke(this, new JoypadControllerEventArgs(e.Controller)); }; diff --git a/src/Joypad/Platforms/Windows/XInputController.cs b/src/Joypad/Platforms/Windows/XInputController.cs index b81c4c3..25f799b 100644 --- a/src/Joypad/Platforms/Windows/XInputController.cs +++ b/src/Joypad/Platforms/Windows/XInputController.cs @@ -18,7 +18,7 @@ internal class XInputController : JoypadController private readonly int _controllerIndex; private readonly XInputCapabilities _capabilities; - private XInputGamepad? _state = null; + private XInputGamepad? _state; internal XInputController(int controllerIndex, XInputCapabilities capabilities) { @@ -54,7 +54,7 @@ internal XInputController(int controllerIndex, XInputCapabilities capabilities) case XInputGamepadB: case XInputGamepadX: case XInputGamepadY: - return GetButtonValue(XInputGamepadDPadUp); + return GetButtonValue(control.Id); case LeftThumbX: return _state.Value.ThumbLX; @@ -76,7 +76,6 @@ internal XInputController(int controllerIndex, XInputCapabilities capabilities) default: return null; - } } @@ -86,25 +85,25 @@ private void AddControls(XInputCapabilities capabilities) { if (HasButton(capabilities.Gamepad, XInputGamepadDPadUp)) { - var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadUp, "Directional Pad", DirectionalPadDirection.Up); + var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadUp, "D-Pad Up", DirectionalPadDirection.Up); AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadDPadDown)) { - var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadDown, "Directional Pad", DirectionalPadDirection.Down); + var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadDown, "D-Pad Down", DirectionalPadDirection.Down); AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadDPadLeft)) { - var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadLeft, "Directional Pad", DirectionalPadDirection.Left); + var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadLeft, "D-Pad Left", DirectionalPadDirection.Left); AddControl(control); } if (HasButton(capabilities.Gamepad, XInputGamepadDPadRight)) { - var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadRight, "Directional Pad", DirectionalPadDirection.Right); + var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadRight, "D-Pad Right", DirectionalPadDirection.Right); AddControl(control); }