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/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/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/JoypadController.cs b/src/Joypad/JoypadController.cs
index 2d0c16b..54eb829 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)
{
@@ -61,12 +71,16 @@ public bool TryGetControl(int controlId, [NotNullWhen(true)] out Control? contro
internal void Initialize()
{
+ UpdateState();
+
foreach (var control in _controls)
{
control.Value = GetValue(control);
}
}
+ 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 b66e0bb..513d900 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,50 @@ 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;
+ e.Controller.Initialize();
+
+ 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()
@@ -69,10 +93,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)
@@ -82,39 +103,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..d863a99
--- /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..a0db610
--- /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..b9ae046
--- /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/XInputControl.cs b/src/Joypad/Platforms/Windows/XInputControl.cs
new file mode 100644
index 0000000..d820990
--- /dev/null
+++ b/src/Joypad/Platforms/Windows/XInputControl.cs
@@ -0,0 +1,27 @@
+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;
+ Id = inputId;
+
+ _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
new file mode 100644
index 0000000..25f799b
--- /dev/null
+++ b/src/Joypad/Platforms/Windows/XInputController.cs
@@ -0,0 +1,255 @@
+using System.Runtime.Versioning;
+using OldBit.Joypad.Controls;
+using OldBit.Joypad.Platforms.Windows.Interop;
+using static OldBit.Joypad.Platforms.Windows.Interop.XInput;
+
+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 XInputGamepad? _state;
+
+ internal XInputController(int controllerIndex, XInputCapabilities capabilities)
+ {
+ _controllerIndex = controllerIndex;
+ _capabilities = capabilities;
+
+ AddControls(capabilities);
+
+ Name = CreateName();
+ Id = CreateDeviceUniqueId();
+ }
+
+ protected override int? GetValue(Control control)
+ {
+ if (_state == null)
+ {
+ return null;
+ }
+
+ 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(control.Id);
+
+ 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;
+ }
+ }
+
+ protected override void UpdateState() => _state = GetState(_controllerIndex);
+
+ private void AddControls(XInputCapabilities capabilities)
+ {
+ if (HasButton(capabilities.Gamepad, XInputGamepadDPadUp))
+ {
+ var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadUp, "D-Pad Up", DirectionalPadDirection.Up);
+ AddControl(control);
+ }
+
+ if (HasButton(capabilities.Gamepad, XInputGamepadDPadDown))
+ {
+ var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadDown, "D-Pad Down", DirectionalPadDirection.Down);
+ AddControl(control);
+ }
+
+ if (HasButton(capabilities.Gamepad, XInputGamepadDPadLeft))
+ {
+ var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadLeft, "D-Pad Left", DirectionalPadDirection.Left);
+ AddControl(control);
+ }
+
+ if (HasButton(capabilities.Gamepad, XInputGamepadDPadRight))
+ {
+ var control = XInputControl.CreateDirectionalPad(XInputGamepadDPadRight, "D-Pad Right", DirectionalPadDirection.Right);
+ AddControl(control);
+ }
+
+ if (HasButton(capabilities.Gamepad, XInputGamepadStart))
+ {
+ var control = XInputControl.CreateButton(XInputGamepadStart, "Start");
+ AddControl(control);
+ }
+
+ if (HasButton(capabilities.Gamepad, XInputGamepadBack))
+ {
+ var control = XInputControl.CreateButton(XInputGamepadBack, "Back");
+ AddControl(control);
+ }
+
+ if (HasButton(capabilities.Gamepad, XInputGamepadLeftThumb))
+ {
+ var control = XInputControl.CreateButton(XInputGamepadLeftThumb, "Left Thumb");
+ AddControl(control);
+ }
+
+ if (HasButton(capabilities.Gamepad, XInputGamepadRightThumb))
+ {
+ var control = XInputControl.CreateButton(XInputGamepadRightThumb, "Right Thumb");
+ AddControl(control);
+ }
+
+ if (HasButton(capabilities.Gamepad, XInputGamepadLeftShoulder))
+ {
+ var control = XInputControl.CreateButton(XInputGamepadLeftShoulder, "Left Shoulder");
+ AddControl(control);
+ }
+
+ if (HasButton(capabilities.Gamepad, XInputGamepadRightShoulder))
+ {
+ var control = XInputControl.CreateButton(XInputGamepadRightShoulder, "Right Shoulder");
+ AddControl(control);
+ }
+
+ if (HasButton(capabilities.Gamepad, XInputGamepadA))
+ {
+ var control = XInputControl.CreateButton(XInputGamepadA, "A");
+ AddControl(control);
+ }
+
+ if (HasButton(capabilities.Gamepad, XInputGamepadB))
+ {
+ var control = XInputControl.CreateButton(XInputGamepadB, "B");
+ AddControl(control);
+ }
+
+ if (HasButton(capabilities.Gamepad, XInputGamepadX))
+ {
+ var control = XInputControl.CreateButton(XInputGamepadX, "X");
+ AddControl(control);
+ }
+
+ if (HasButton(capabilities.Gamepad, XInputGamepadY))
+ {
+ var control = XInputControl.CreateButton(XInputGamepadY, "Y");
+ AddControl(control);
+ }
+
+ if (capabilities.Gamepad.ThumbLX != 0)
+ {
+ var control = XInputControl.CreateThumbStick(LeftThumbX, "Left Thumb X");
+ AddControl(control);
+ }
+
+ if (capabilities.Gamepad.ThumbLY != 0)
+ {
+ var control = XInputControl.CreateThumbStick(LeftThumbY, "Left Thumb Y");
+ AddControl(control);
+ }
+
+ if (capabilities.Gamepad.ThumbRX != 0)
+ {
+ var control = XInputControl.CreateThumbStick(RightThumbX, "Right Thumb X");
+ AddControl(control);
+ }
+
+ if (capabilities.Gamepad.ThumbRY != 0)
+ {
+ 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);
+ }
+ }
+
+ 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;
+ }
+
+ private static bool HasButton(XInputGamepad gamepad, int button) =>
+ (gamepad.Buttons & button) == button;
+
+ private int GetButtonValue(int controlId) =>
+ ((_state?.Buttons ?? 0) & controlId) != 0 ? 1 : 0;
+}
diff --git a/src/Joypad/Platforms/Windows/XInputDeviceManager.cs b/src/Joypad/Platforms/Windows/XInputDeviceManager.cs
new file mode 100644
index 0000000..8bd8e66
--- /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 = true
+ };
+
+ _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();
+}