mirror of
https://github.com/ayufan/steam-deck-tools.git
synced 2026-04-20 05:33:50 +00:00
SteamController: Add initial DS4 support (with Gyro, Accel, Trackpads and Haptics)
This commit is contained in:
parent
862d7afec5
commit
70237ad9d4
13 changed files with 739 additions and 33 deletions
304
SteamController/Devices/DS4Controller.cs
Normal file
304
SteamController/Devices/DS4Controller.cs
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
using System.Diagnostics;
|
||||
using Nefarius.ViGEm.Client;
|
||||
using Nefarius.ViGEm.Client.Exceptions;
|
||||
using Nefarius.ViGEm.Client.Targets;
|
||||
using Nefarius.ViGEm.Client.Targets.DualShock4;
|
||||
using Nefarius.ViGEm.Client.Targets.Xbox360;
|
||||
using static CommonHelpers.Log;
|
||||
|
||||
namespace SteamController.Devices
|
||||
{
|
||||
public partial class DS4Controller : IDisposable
|
||||
{
|
||||
public readonly TimeSpan FeedbackTimeout = TimeSpan.FromMilliseconds(1000);
|
||||
public const ushort VendorID = 0x054C;
|
||||
public const ushort ProductID = 0x05C4;
|
||||
|
||||
private const int REPORT_SIZE = 63;
|
||||
private const int TIMESTAMP_HZ = 800;
|
||||
private const int TIMESTAMP_INCREMENT = 188;
|
||||
|
||||
private ViGEmClient? client;
|
||||
private IDualShock4Controller? device;
|
||||
private bool isConnected;
|
||||
private byte[] reportBytes = new byte[REPORT_SIZE];
|
||||
private bool submitReport;
|
||||
private int submittedReports = 0;
|
||||
private Stopwatch stopwatch = new Stopwatch();
|
||||
|
||||
public DS4Controller()
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
using (client) { }
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
lock (this) { Fail(); }
|
||||
}
|
||||
|
||||
internal bool Tick()
|
||||
{
|
||||
if (this.device is not null)
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
var client = new ViGEmClient();
|
||||
var device = client.CreateDualShock4Controller();
|
||||
device.AutoSubmitReport = false;
|
||||
device.FeedbackReceived += DS4_FeedbackReceived;
|
||||
this.device = device;
|
||||
this.client = client;
|
||||
return true;
|
||||
}
|
||||
catch (VigemBusNotFoundException)
|
||||
{
|
||||
// ViGem is not installed
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Fail()
|
||||
{
|
||||
var client = this.client;
|
||||
|
||||
// unset current device
|
||||
this.isConnected = false;
|
||||
this.client = null;
|
||||
this.device = null;
|
||||
this.stopwatch.Stop();
|
||||
|
||||
try { using (client) { } }
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
private void PrepareReport()
|
||||
{
|
||||
var oldReportBytes = reportBytes;
|
||||
|
||||
reportBytes = new byte[63];
|
||||
submitReport = false;
|
||||
|
||||
Counter.Set(reportBytes, (byte)((submittedReports << 2) & 0xFF));
|
||||
BatteryLevel.Set(reportBytes, 255);
|
||||
Timestamp.Set(reportBytes, (ushort)(stopwatch.ElapsedMilliseconds * TIMESTAMP_HZ * TIMESTAMP_INCREMENT / 1000));
|
||||
|
||||
DPadReleased.Set(reportBytes);
|
||||
LeftThumbX.SetScaled(reportBytes, 0);
|
||||
LeftThumbY.SetScaled(reportBytes, 0);
|
||||
RightThumbX.SetScaled(reportBytes, 0);
|
||||
RightThumbY.SetScaled(reportBytes, 0);
|
||||
GyroX.Set(reportBytes, 0);
|
||||
GyroY.Set(reportBytes, 0);
|
||||
GyroZ.Set(reportBytes, 0);
|
||||
AccelX.Set(reportBytes, 0);
|
||||
AccelY.Set(reportBytes, 0);
|
||||
AccelZ.Set(reportBytes, 0);
|
||||
|
||||
// Copy trackpad packets
|
||||
reportBytes[32] = (byte)((reportBytes[32] + 1) & 0x3);
|
||||
Array.Copy(oldReportBytes, 33, reportBytes, 33, 9);
|
||||
Array.Copy(oldReportBytes, 33, reportBytes, 42, 9 * 2);
|
||||
}
|
||||
|
||||
internal void BeforeUpdate()
|
||||
{
|
||||
device?.ResetReport();
|
||||
|
||||
if (!isConnected)
|
||||
{
|
||||
FeedbackLargeMotor = null;
|
||||
FeedbackSmallMotor = null;
|
||||
FeedbackReceived = null;
|
||||
LightbarColor = null;
|
||||
}
|
||||
|
||||
PrepareReport();
|
||||
Connected = false;
|
||||
}
|
||||
|
||||
private void SetConnected(bool wantsConnected)
|
||||
{
|
||||
if (wantsConnected == isConnected)
|
||||
return;
|
||||
|
||||
if (wantsConnected)
|
||||
{
|
||||
try
|
||||
{
|
||||
device?.Connect();
|
||||
stopwatch.Restart();
|
||||
TraceLine("Connected DS4 Controller.");
|
||||
}
|
||||
catch (System.ComponentModel.Win32Exception e)
|
||||
{
|
||||
// This is expected exception (as sometimes device will fail to connect)
|
||||
// ERROR_SUCCESS, which likely means COM did not succeed
|
||||
if (e.NativeErrorCode == 0)
|
||||
DebugException("DS4", "ConnectExpected", e);
|
||||
else
|
||||
TraceException("DS4", "ConnectExpected", e);
|
||||
Fail();
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
TraceException("DS4", "Connect", e);
|
||||
Fail();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
device?.Disconnect();
|
||||
stopwatch.Stop();
|
||||
TraceLine("Disconnected DS4 Controller.");
|
||||
}
|
||||
catch (VigemTargetNotPluggedInException)
|
||||
{
|
||||
// everything fine
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
TraceException("DS4", "Disconnect", e);
|
||||
Fail();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
isConnected = wantsConnected;
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
if (device is not null && Connected != isConnected)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
SetConnected(Connected);
|
||||
}
|
||||
}
|
||||
|
||||
if (isConnected && submitReport)
|
||||
{
|
||||
try
|
||||
{
|
||||
device?.SubmitRawReport(reportBytes);
|
||||
submittedReports++;
|
||||
}
|
||||
catch (VigemInvalidTargetException)
|
||||
{
|
||||
// Device was lost
|
||||
lock (this) { Fail(); }
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
TraceException("DS4", "SubmitReport", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (FeedbackReceived is not null && FeedbackReceived.Value.Add(FeedbackTimeout) < DateTime.Now)
|
||||
{
|
||||
FeedbackLargeMotor = null;
|
||||
FeedbackSmallMotor = null;
|
||||
FeedbackReceived = null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Valid
|
||||
{
|
||||
get { return device is not null; }
|
||||
}
|
||||
|
||||
public bool Connected { get; set; }
|
||||
public byte? FeedbackLargeMotor { get; private set; }
|
||||
public byte? FeedbackSmallMotor { get; private set; }
|
||||
public LightbarColor? LightbarColor { get; private set; }
|
||||
public DateTime? FeedbackReceived { get; private set; }
|
||||
|
||||
public bool this[DualShock4Button button]
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
button.Set(reportBytes, value);
|
||||
submitReport = true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool this[DualShock4DPadDirection button]
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
button.Set(reportBytes);
|
||||
submitReport = true;
|
||||
}
|
||||
}
|
||||
|
||||
public short this[DualShock4Axis axis]
|
||||
{
|
||||
set
|
||||
{
|
||||
axis.SetScaled(reportBytes, value);
|
||||
submitReport = true;
|
||||
}
|
||||
}
|
||||
|
||||
public short this[DualShock4Slider slider]
|
||||
{
|
||||
set
|
||||
{
|
||||
slider.Set(reportBytes, value);
|
||||
submitReport = true;
|
||||
}
|
||||
}
|
||||
|
||||
public short this[DualShock4Sensor sensor]
|
||||
{
|
||||
set
|
||||
{
|
||||
sensor.Set(reportBytes, value);
|
||||
submitReport = true;
|
||||
}
|
||||
}
|
||||
|
||||
public Point? this[DualShock4Finger finger]
|
||||
{
|
||||
set
|
||||
{
|
||||
finger.Set(reportBytes, value);
|
||||
submitReport = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Overwrite(DualShock4Button button, bool value, int minPressedTime = 0)
|
||||
{
|
||||
button.Set(reportBytes, value);
|
||||
submitReport = true;
|
||||
}
|
||||
|
||||
public void ResetFeedback()
|
||||
{
|
||||
FeedbackReceived = null;
|
||||
}
|
||||
|
||||
private void DS4_FeedbackReceived(object sender, DualShock4FeedbackReceivedEventArgs e)
|
||||
{
|
||||
FeedbackLargeMotor = e.LargeMotor;
|
||||
FeedbackSmallMotor = e.SmallMotor;
|
||||
LightbarColor = e.LightbarColor;
|
||||
FeedbackReceived = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue