• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

luttje / Key2Joy / 6598306412

21 Oct 2023 03:54PM UTC coverage: 45.09% (-7.4%) from 52.519%
6598306412

Pull #50

github

web-flow
Merge bff596568 into 14b7ce9a7
Pull Request #50: Add XInput in preparation for gamepad triggers + add xmldoc

751 of 2289 branches covered (0.0%)

Branch coverage included in aggregate %.

2384 of 2384 new or added lines in 80 files covered. (100.0%)

3845 of 7904 relevant lines covered (48.65%)

15964.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.12
/Core/Key2Joy.Core/LowLevelInput/XInput/XInputService.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using System.Threading;
5
using Timer = System.Timers.Timer;
6

7
namespace Key2Joy.LowLevelInput.XInput;
8

9
public class XInputService : IXInputService
10
{
11
    private const string GAMEPAD_NAME = "Physical";
12

13
    public const int MaxDevices = 4;
14
    private const int UpdateIntervalInMs = 20;
15

16
    /// <summary>
17
    /// Called when the state of a device changes.
18
    /// </summary>
19
    public event EventHandler<DeviceStateChangedEventArgs> StateChanged;
20

21
    /// <summary>
22
    /// Called whenever the device sends a new packet
23
    /// </summary>
24
    public event EventHandler<DevicePacketReceivedEventArgs> PacketReceived;
25

26
    private readonly IXInput xInputInstance;
27
    private readonly Dictionary<int, IGamePadInfo> registeredDevices;
28
    private readonly Dictionary<int, Timer> vibrationTimers;
29
    private readonly Dictionary<int, XInputState> lastStates;
30
    private Thread pollingThread;
31
    private bool isPolling;
32

33
    public XInputService(IXInput xinputInstance = null)
13✔
34
    {
35
        this.xInputInstance = xinputInstance ?? new NativeXInput();
13✔
36
        this.registeredDevices = new();
13✔
37
        this.vibrationTimers = new();
13✔
38
        this.lastStates = new();
13✔
39
    }
13✔
40

41
    private bool GetIsDeviceConnected(int deviceIndex)
42
    {
43
        var state = new XInputState();
8✔
44
        var resultCode = this.xInputInstance.XInputGetState(deviceIndex, ref state);
8✔
45

46
        return resultCode == XInputResultCode.ERROR_SUCCESS;
8✔
47
    }
48

49
    /// <inheritdoc/>
50
    public void RecognizePhysicalDevices()
51
    {
52
        // If we've started polling then what is connected is what is connected.
53
        if (this.isPolling)
2!
54
        {
55
            return;
×
56
        }
57

58
        lock (this.registeredDevices)
2✔
59
        {
60
            this.registeredDevices.Clear();
2✔
61

62
            for (var i = 0; i < MaxDevices; i++)
20✔
63
            {
64
                this.RegisterDevice(i);
8✔
65
            }
66
        }
2✔
67
    }
2✔
68

69
    /// <summary>
70
    /// Registers a device by its index for monitoring its state.
71
    /// </summary>
72
    /// <param name="deviceIndex">The index of the device to register.</param>
73
    private void RegisterDevice(int deviceIndex)
74
    {
75
        if (!this.GetIsDeviceConnected(deviceIndex))
8!
76
        {
77
            return;
8✔
78
        }
79

80
        this.registeredDevices.Add(deviceIndex, new GamePadInfo(deviceIndex, GAMEPAD_NAME));
×
81
    }
×
82

83
    /// <inheritdoc/>
84
    public void StartPolling()
85
    {
86
        if (this.isPolling)
2!
87
        {
88
            return;
×
89
        }
90

91
        this.isPolling = true;
2✔
92

93
        this.pollingThread = new Thread(() =>
2✔
94
        {
2✔
95
            lock (this.registeredDevices)
2✔
96
            {
2✔
97
                do
2✔
98
                {
2✔
99
                    // Add a delay to avoid hammering the IXInput instance too rapidly
2✔
100
                    Thread.Sleep(UpdateIntervalInMs);
3✔
101

2✔
102
                    foreach (var device in this.registeredDevices.Values)
6✔
103
                    {
2✔
104
                        var deviceIndex = device.Index;
×
105
                        var newState = new XInputState();
×
106
                        var resultCode = this.xInputInstance.XInputGetState(deviceIndex, ref newState);
×
107

2✔
108
                        if (resultCode != XInputResultCode.ERROR_SUCCESS)
×
109
                        {
2✔
110
                            continue;
2✔
111
                        }
2✔
112

2✔
113
                        this.PacketReceived?.Invoke(this, new DevicePacketReceivedEventArgs(deviceIndex, newState));
×
114
                        var hasLastState = this.lastStates.TryGetValue(deviceIndex, out var lastState);
×
115

2✔
116
                        if (!hasLastState || !lastState.Equals(newState))
×
117
                        {
2✔
118
                            if (!hasLastState)
×
119
                            {
2✔
120
                                this.lastStates.Add(deviceIndex, newState);
×
121
                            }
2✔
122
                            else
2✔
123
                            {
2✔
124
                                this.lastStates[deviceIndex] = newState;
×
125
                                this.StateChanged?.Invoke(this, new DeviceStateChangedEventArgs(deviceIndex, newState));
×
126
                                device.OnActivityOccurred();
×
127
                            }
2✔
128
                        }
2✔
129
                    }
2✔
130
                } while (this.isPolling);
3✔
131
            }
2✔
132
        });
4✔
133

134
        this.pollingThread.Start();
2✔
135
    }
2✔
136

137
    /// <inheritdoc/>
138
    public void StopPolling()
139
        => this.isPolling = false;
2✔
140

141
    /// <inheritdoc/>
142
    public XInputState? GetState(int deviceIndex)
143
    {
144
        if (!this.registeredDevices.ContainsKey(deviceIndex))
1!
145
        {
146
            // Only return the state for devices registered before simulated
147
            // devices were added. XInputGetState also returns those, so we
148
            // need to check it manually.
149
            return null;
1✔
150
        }
151

152
        var inputState = new XInputState();
×
153
        this.xInputInstance.XInputGetState(deviceIndex, ref inputState);
×
154

155
        return inputState;
×
156
    }
157

158
    /// <inheritdoc/>
159
    public XInputCapabilities GetCapabilities(int deviceIndex)
160
    {
161
        var capabilities = new XInputCapabilities();
1✔
162

163
        this.xInputInstance.XInputGetCapabilities(deviceIndex, ref capabilities);
1✔
164

165
        return capabilities;
1✔
166
    }
167

168
    /// <inheritdoc/>
169
    public XInputBatteryInformation GetBatteryInformation(int deviceIndex, BatteryDeviceType deviceType)
170
    {
171
        var batteryInformation = new XInputBatteryInformation();
1✔
172

173
        this.xInputInstance.XInputGetBatteryInformation(deviceIndex, deviceType, ref batteryInformation);
1✔
174

175
        return batteryInformation;
1✔
176
    }
177

178
    /// <inheritdoc/>
179
    public XInputKeystroke GetKeystroke(int deviceIndex)
180
    {
181
        var keystrokeData = new XInputKeystroke();
1✔
182

183
        this.xInputInstance.XInputGetKeystroke(deviceIndex, 0, ref keystrokeData);
1✔
184

185
        return keystrokeData;
1✔
186
    }
187

188
    /// <inheritdoc/>
189
    public void Vibrate(int deviceIndex, double leftMotorSpeedFraction, double rightMotorSpeedFraction, TimeSpan duration)
190
    {
191
        var vibrationInfo = new XInputVibration(leftMotorSpeedFraction, rightMotorSpeedFraction);
2✔
192
        this.xInputInstance.XInputSetState(deviceIndex, ref vibrationInfo);
2✔
193

194
        // If a timer for this device is already running, stop it first
195
        if (this.vibrationTimers.ContainsKey(deviceIndex))
2!
196
        {
197
            var existingTimer = this.vibrationTimers[deviceIndex];
×
198
            existingTimer.Stop();
×
199
            existingTimer.Dispose();
×
200
        }
201

202
        // Create a new timer for this vibration
203
        var timer = new Timer(duration.TotalMilliseconds);
2✔
204
        timer.Elapsed += (sender, e) => this.StopVibration(deviceIndex);
4✔
205
        timer.AutoReset = false;
2✔
206
        timer.Start();
2✔
207

208
        this.vibrationTimers[deviceIndex] = timer;
2✔
209
    }
2✔
210

211
    /// <inheritdoc/>
212
    public void StopVibration(int deviceIndex)
213
    {
214
        var vibrationInfo = new XInputVibration(0, 0);
3✔
215
        this.xInputInstance.XInputSetState(deviceIndex, ref vibrationInfo);
3✔
216

217
        if (this.vibrationTimers.ContainsKey(deviceIndex))
3✔
218
        {
219
            var timer = this.vibrationTimers[deviceIndex];
2✔
220
            timer.Stop();
2✔
221
            timer.Dispose();
2✔
222
            this.vibrationTimers.Remove(deviceIndex);
2✔
223
        }
224
    }
3✔
225

226
    /// <inheritdoc/>
227
    public IList<IGamePadInfo> GetActiveDevicesInfo()
228
        => this.registeredDevices.Values.ToList();
1✔
229
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc